/*
 * piweb — Discord-flavoured dark UI, phone first.
 *
 * Layout rules that matter on iOS Safari:
 *  - 100dvh (not 100vh) so the URL bar collapsing doesn't hide the composer
 *  - env(safe-area-inset-*) padding for the notch and home indicator
 *  - 16px input font, otherwise iOS zooms the page on focus
 */

:root {
  color-scheme: dark;
  --bg: #313338;
  --bg-alt: #2b2d31;
  --bg-dark: #1e1f22;
  --bg-input: #383a40;
  --bg-hover: rgb(255 255 255 / 0.1);
  --bg-3: #2b2d31;
  --border: #3f4147;
  --text: #dbdee1;
  --text-2: #b5bac1;
  --text-dim: #949ba4;
  --text-bright: #f2f3f5;
  --accent: #5865f2;
  --accent-hover: #4752c4;
  --accent-light: #c4b5fd;
  --danger: #da373c;
  --thinking: #a78bfa;
  --tool: #3ba55d;
  --radius: 8px;
  --topbar-h: 48px;
}

:root[data-theme='light'] {
  color-scheme: light;
  /* Japanese minimal: white washi, warm stone neutrals and one quiet aizome
     accent. Surfaces stay flat so spacing and hairlines create the hierarchy. */
  --bg: #ffffff;
  --bg-alt: #fbfaf7;
  --bg-dark: #ebe8e1;
  --bg-input: #f4f2ed;
  --bg-hover: rgb(45 43 38 / 0.055);
  --bg-3: #f4f2ed;
  --border: #dedbd3;
  --text: #35332f;
  --text-2: #56534d;
  --text-dim: #74716a;
  --text-bright: #1f1e1a;
  --accent: #3f5f6f;
  --accent-hover: #324f5e;
  --accent-light: #557789;
  --danger: #ad4038;
  --thinking: #6e5c87;
  --tool: #397057;
}

* {
  box-sizing: border-box;
}

/*
 * The UA rule for [hidden] is `display: none`, but ANY author rule that sets
 * display (.login{display:grid}, .app{display:flex}, .typing, .autocomplete, …)
 * beats it — author styles win over the UA sheet regardless of specificity.
 * Without this the login overlay renders on top of the app forever even though
 * `hidden` is set. Keep this above every display rule.
 */
[hidden] {
  display: none !important;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  overscroll-behavior: none;
}

/*
 * iOS 26 Home Screen app (viewport-fit=cover + black-translucent): WebKit sizes
 * the viewport short by the status bar height while the page is shorter than
 * the screen (WebKit 301108), leaving a strip under the composer until the
 * phone is rotated. A page at least screen-tall gets the full viewport, and
 * 100dvh follows. Making the shell itself taller does not work: WebKit stops
 * drawing at the short edge. min() caps it at 100lvh so an opaque status bar
 * (inset 0, WebKit 316008) never makes the page scroll.
 */
@media (display-mode: standalone) {
  html {
    min-height: min(100lvh, calc(100% + env(safe-area-inset-top)));
  }
}

body {
  background: var(--bg);
  color: var(--text);
  font-family:
    'gg sans',
    -apple-system,
    BlinkMacSystemFont,
    'Segoe UI',
    Roboto,
    'Helvetica Neue',
    sans-serif;
  font-size: 15px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

button {
  font: inherit;
  color: inherit;
  cursor: pointer;
}

svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ── login ───────────────────────────────────────────────────────────── */

.login {
  display: grid;
  place-items: center;
  height: 100dvh;
  padding: 24px;
  background: var(--bg-dark);
}

.login-card {
  width: 100%;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 28px 24px;
  background: var(--bg-alt);
  border-radius: 12px;
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.4);
  text-align: center;
}

.login-logo {
  width: 56px;
  height: 56px;
  margin: 0 auto;
  display: grid;
  place-items: center;
  font-size: 30px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
}

.login-card h1 {
  margin: 4px 0 0;
  font-size: 22px;
  color: var(--text-bright);
}

.login-sub {
  margin: 0 0 8px;
  color: var(--text-dim);
  font-size: 14px;
}

.login-card input,
.composer textarea {
  width: 100%;
  padding: 12px 14px;
  background: var(--bg-input);
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--text-bright);
  font-size: 16px; /* < 16px makes iOS zoom on focus */
  outline: none;
}

.login-card input:focus {
  border-color: var(--accent);
}

.btn-primary {
  padding: 12px 14px;
  background: var(--accent);
  border: 0;
  border-radius: var(--radius);
  color: #fff;
  font-size: 15px;
  font-weight: 600;
}

.btn-primary:active {
  background: var(--accent-hover);
}

.login-error {
  margin: 0;
  color: #f87171;
  font-size: 14px;
}

/* ── shell ───────────────────────────────────────────────────────────── */

.app {
  height: 100dvh;
  display: flex;
  overflow: hidden;
}

/*
 * Keyboard up: pin the shell to the visual viewport (viewport-shell.js), so
 * iOS panning the page cannot reveal a blank band under the composer. The
 * keyboard covers the home indicator, so its safe-area padding goes too.
 */
html.keyboard-shell .app {
  position: fixed;
  top: var(--keyboard-shell-top, 0px);
  left: 0;
  right: 0;
  height: var(--keyboard-shell-height, 100dvh);
}

html.keyboard-shell .composer-wrap {
  padding-bottom: 10px;
}

/* A keyboard move iOS reports all at once eases along its pan (viewport-shell.js). */
html.keyboard-shell-easing .app {
  transition:
    top var(--keyboard-shell-ease),
    height var(--keyboard-shell-ease);
}

html.keyboard-shell-easing .composer-wrap {
  transition: padding-bottom var(--keyboard-shell-ease);
}

html.keyboard-shell-leaving .composer-wrap {
  padding-bottom: calc(10px + env(safe-area-inset-bottom));
}

/* Placed at 42% of the full screen, it would sit on the pinned topbar. */
html.keyboard-shell .life-edge-hint {
  display: none;
}

/* Life is a protected presentation of one persistent default-model channel. */
.life-edge-hint {
  position: fixed;
  z-index: 30;
  top: 42%;
  right: max(0px, env(safe-area-inset-right));
  width: 48px;
  height: 64px;
  padding: 0;
  display: grid;
  place-items: center;
  border: 0;
  background: transparent;
  color: color-mix(in srgb, var(--tool) 82%, var(--text));
  opacity: 0.86;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  will-change: transform;
  transition: opacity 0.18s ease, transform 0.18s cubic-bezier(0.2, 0, 0, 1), color 0.18s ease;
}

.life-edge-hint:hover {
  opacity: 1;
  color: var(--text-bright);
  transform: translateX(-3px);
}

.life-edge-hint:hover .life-edge-drop {
  fill: color-mix(in srgb, var(--tool) 12%, var(--bg-alt));
  stroke: color-mix(in srgb, var(--tool) 70%, transparent);
}

.life-edge-hint svg {
  width: 48px;
  height: 64px;
  overflow: visible;
  filter: drop-shadow(-4px 3px 8px rgb(0 0 0 / 0.2));
}

.life-edge-drop {
  fill: color-mix(in srgb, var(--bg-alt) 92%, transparent);
  stroke: color-mix(in srgb, var(--tool) 50%, transparent);
  stroke-width: 1.4;
  vector-effect: non-scaling-stroke;
  transition: fill 0.18s ease, stroke 0.18s ease;
}

.life-edge-leaf {
  fill: none;
  stroke: currentcolor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.life-edge-hint:active .life-edge-drop {
  fill: color-mix(in srgb, var(--tool) 17%, var(--bg-alt));
  stroke: color-mix(in srgb, var(--tool) 78%, transparent);
}

.life-edge-hint:active {
  color: var(--text-bright);
  transform: translateX(-1px);
}

.life-edge-hint:focus-visible {
  outline: none;
}

.life-edge-hint:focus-visible .life-edge-drop {
  stroke: var(--tool);
  stroke-width: 2.6;
}

.life-swipe-preview {
  position: fixed;
  z-index: 53;
  inset: 0;
  display: grid;
  place-items: center;
  width: 100vw;
  height: 100dvh;
  background:
    radial-gradient(circle at 70% 34%, rgb(81 137 91 / 0.22), transparent 36%), var(--bg-alt);
  border-left: 1px solid color-mix(in srgb, var(--tool) 55%, transparent);
  box-shadow: -18px 0 42px rgb(0 0 0 / 0.28);
  pointer-events: none;
  will-change: transform, opacity;
  backface-visibility: hidden;
  contain: paint;
}

.life-swipe-preview.settling {
  pointer-events: auto;
}

/* During a Life pull, the current page—not the destination overlay—tracks the
   finger. The stationary Life preview sits underneath and is progressively
   revealed from the right. The water drop is nested in .main, so its rounded
   body and edge-attached point inherit the exact same page transform. */
.app.life-page-dragging .main {
  z-index: 54;
  background: var(--bg);
  box-shadow: 18px 0 42px rgb(0 0 0 / 0.3);
  will-change: transform;
}

.app.life-page-dragging .life-edge-hint {
  z-index: 55;
  transition: none;
}

.life-swipe-mark {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: var(--text-bright);
}

.life-swipe-mark svg {
  width: 48px;
  height: 48px;
  color: var(--tool);
}

.life-swipe-mark strong {
  font-size: 22px;
}

.life-swipe-mark span {
  color: var(--text-dim);
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.life-swipe-cancel {
  min-width: 88px;
  min-height: 44px;
  margin-top: 16px;
  padding: 0 18px;
  border: 1px solid color-mix(in srgb, var(--text-dim) 55%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--bg-input) 88%, transparent);
  color: var(--text-2);
  font: inherit;
}

.life-swipe-cancel:active {
  background: var(--bg-hover);
  color: var(--text-bright);
}

.app.life-mode .drawer,
.app.life-mode #btn-menu,
.app.life-mode #btn-gpt-usage,
.app.life-mode #btn-model,
.app.life-mode .topbar-title > .hash,
.app.life-mode .life-edge-hint {
  display: none;
}

.app.life-mode .topbar {
  position: relative;
  padding-right: max(4px, env(safe-area-inset-right));
}

/* Keep Status, Thinking, Life reset, safe overflow, and emergency Stop on the right;
   the absolutely centered title must never cover these hit targets. Status is
   the first right-side action while idle; Stop takes that role while busy. */
.app.life-mode #btn-status,
.app.life-mode #btn-stop:not([hidden]) {
  margin-left: auto;
}

.app.life-mode #btn-status[hidden] ~ #btn-thinking:not([hidden]),
.app.life-mode #btn-status[hidden] ~ #btn-life-new-session {
  margin-left: auto;
}

.app.life-mode #btn-stop:not([hidden]) ~ #btn-status,
.app.life-mode #btn-stop:not([hidden]) ~ #btn-thinking,
.app.life-mode #btn-stop:not([hidden]) ~ #btn-life-new-session,
.app.life-mode #btn-status:not([hidden]) ~ #btn-thinking,
.app.life-mode #btn-status:not([hidden]) ~ #btn-life-new-session,
.app.life-mode #btn-thinking:not([hidden]) ~ #btn-life-new-session,
.app.life-mode #btn-more {
  margin-left: 0;
}

.app.life-mode .topbar-title {
  position: absolute;
  left: 50%;
  max-width: min(54vw, 240px);
  transform: translateX(-50%);
  justify-content: center;
}

/* Busy Life has five mandatory right-side actions, so it always uses normal
   flex flow instead of an absolute title. Compact the labelled back action and
   decorative leaf only where they would squeeze the full “Life” name; idle Life
   needs normal flow plus the same compaction across phone widths. */
.app.life-mode.agent-busy .topbar-title {
  position: static;
  left: auto;
  flex: 0 1 auto;
  max-width: none;
  transform: none;
}

.app.life-mode.agent-busy .life-title-mark {
  display: none;
}

@media (max-width: 420px) {
  .app.life-mode .topbar-title {
    gap: 3px;
  }

  .app.life-mode .life-title-mark {
    display: none;
  }

  .app.life-mode.agent-busy .life-back {
    width: 34px;
    padding-inline: 0;
    justify-content: center;
  }

  .app.life-mode.agent-busy .life-back span {
    display: none;
  }
}

@media (max-width: 380px) {
  .app.life-mode .topbar-title {
    position: static;
    left: auto;
    flex: 0 1 auto;
    max-width: none;
    transform: none;
  }

  .app.life-mode .life-back {
    width: 34px;
    padding-inline: 0;
    justify-content: center;
  }

  .app.life-mode .life-back span,
  .app.life-mode .life-title-mark {
    display: none;
  }
}

.app.life-mode #session-name {
  cursor: default;
  margin-left: 0;
  padding-inline: 2px;
}

.app.life-mode #header-badge {
  display: none;
}

.app.life-mode #session-name:hover,
.app.life-mode #session-name:focus-visible {
  background: transparent;
}

.life-back {
  display: flex;
  align-items: center;
  gap: 2px;
  height: 38px;
  padding: 0 7px 0 2px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-2);
}

.life-back:active {
  background: var(--bg-input);
  color: var(--text-bright);
}

.life-back svg,
.life-title-mark svg {
  width: 18px;
  height: 18px;
}

.life-title-mark {
  display: inline-grid;
  place-items: center;
  color: var(--tool);
}

@media (min-width: 768px) {
  .life-swipe-preview {
    display: none;
  }
}

.main {
  position: relative; /* containing block for .jump-live */
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

.topbar {
  display: flex;
  align-items: center;
  gap: 8px;
  height: calc(var(--topbar-h) + env(safe-area-inset-top));
  padding-top: env(safe-area-inset-top);
  padding-left: max(8px, env(safe-area-inset-left));
  padding-right: max(8px, env(safe-area-inset-right));
  background: var(--bg);
  border-bottom: 1px solid var(--bg-dark);
  flex: none;
}

@media (min-width: 600px) {
  .topbar {
    padding-left: max(60px, calc(env(safe-area-inset-left) + 12px));
  }
}

.topbar-title {
  flex: 1;
  min-width: 0;
  /* Never let the badge spill under the stop button when space runs out. */
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: 4px;
  font-weight: 600;
  color: var(--text-bright);
}

#session-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 2px 6px;
  margin-left: -6px;
  border-radius: 6px;
  cursor: text;
}

#session-name:hover,
#session-name:focus-visible {
  background: var(--bg-input);
  outline: none;
}

/* Inline rename: an in-place field rather than a native prompt(), which on iOS
   is a jarring system sheet that hides the thing being renamed. */
.name-edit {
  flex: 1;
  min-width: 0;
  padding: 3px 6px;
  background: var(--bg-input);
  border: 1px solid var(--accent);
  border-radius: 6px;
  color: var(--text-bright);
  font: inherit;
  font-size: 16px; /* keep iOS from zooming */
  outline: none;
}

.hash {
  color: var(--text-dim);
  font-size: 20px;
  cursor: pointer;
  user-select: none;
  padding: 2px 4px;
  border-radius: 4px;
  transition:
    color 0.15s ease,
    background-color 0.15s ease;
}

.hash:hover,
.hash:active {
  color: var(--text-bright);
  background: var(--bg-input);
}

.icon-btn {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex: none;
  background: transparent;
  border: 0;
  border-radius: var(--radius);
  color: var(--text-dim);
}

.icon-btn:active {
  background: var(--bg-input);
  color: var(--text-bright);
}

.icon-btn.send {
  color: var(--accent);
}

/* Primary turn actions must be reliably tappable on phone layouts. */
@media (pointer: coarse) {
  #btn-send,
  #btn-stop {
    min-width: 44px;
    min-height: 44px;
  }
}

/* ── drawer ──────────────────────────────────────────────────────────── */

.drawer {
  position: fixed;
  /* Above transcript controls (composer 40, menu 45, jump 50), but below the
     lightbox (60) and toast (70). An open drawer must own every tap in its area. */
  z-index: 55;
  top: 0;
  left: 0;
  width: 78%;
  max-width: 300px;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg-alt);
  transform: translateX(-100%);
  transition: transform 0.18s ease;
  padding-top: env(safe-area-inset-top);
  /* Its list scrolls up and down only; a sideways drag is the drawer's own
     (pushing it back), so the browser never starts a scroll for it and the
     drag needs no blocking touch listener (app.js onEdgeTouchMove). */
  touch-action: pan-y pinch-zoom;
}

.drawer.open {
  transform: translateX(0);
}

.scrim {
  position: fixed;
  inset: 0;
  z-index: 54;
  background: rgb(0 0 0 / 0.5);
  /* Fades with the drawer; during an edge swipe JS drives opacity directly. */
  transition: opacity 0.18s ease;
}

.drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--topbar-h);
  padding: 0 8px 0 16px;
  border-bottom: 1px solid var(--bg-dark);
  flex: none;
}

.host-metrics {
  display: flex;
  flex-direction: column;
  padding: 8px 14px;
  border-bottom: 1px solid var(--bg-dark);
  flex: none;
  font-size: 11px;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
  user-select: none;
}
.host-metrics[hidden] { display: none; }

.host-metrics-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  padding: 3px 6px;
  margin: -3px -6px;
  background: transparent;
  border: none;
  border-radius: 6px;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s ease;
}
.host-metrics-summary:hover {
  background: var(--bg-hover, rgba(255, 255, 255, 0.05));
}
.host-metrics-summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.host-metrics-summary[disabled] {
  cursor: default;
  background: transparent;
}

.host-metrics-primary {
  display: flex;
  align-items: baseline;
  gap: 8px 12px;
  flex: 1;
  flex-wrap: wrap;
  min-width: 0;
}

.host-metrics-primary .host-metric {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
}

.host-metrics-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  color: var(--text-3);
  padding: 2px;
}
.host-metrics-arrow svg {
  width: 14px;
  height: 14px;
  transition: transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
.host-metrics.expanded .host-metrics-arrow svg,
.host-metrics[data-expanded="true"] .host-metrics-arrow svg {
  transform: rotate(180deg);
}

.host-metrics-details {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.24s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.2s ease;
  opacity: 0;
}
.host-metrics.expanded .host-metrics-details,
.host-metrics[data-expanded="true"] .host-metrics-details {
  grid-template-rows: 1fr;
  opacity: 1;
}

.host-metrics-details-inner {
  overflow: hidden;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px 12px;
  padding-top: 0;
  transition: padding-top 0.24s cubic-bezier(0.16, 1, 0.3, 1);
}
.host-metrics.expanded .host-metrics-details-inner,
.host-metrics[data-expanded="true"] .host-metrics-details-inner {
  padding-top: 8px;
}

.host-metric { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px; min-width: 0; }
.host-metric strong { font-size: 12px; font-weight: 500; color: var(--text-bright); white-space: nowrap; }
.metrics-interval {
  width: 56px;
  min-height: 44px;
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 16px;
  text-align: right;
  outline: none;
}
.metrics-interval:focus { color: var(--text-bright); }

.drawer-title {
  font-weight: 700;
  color: var(--text-bright);
}

.drawer-actions {
  display: flex;
  align-items: center;
}

.session-list {
  flex: 1;
  overflow-y: auto;
  padding: 6px;
  -webkit-overflow-scrolling: touch;
}

/* One-line row: state | name … model tag | dashboard toggle | delete.
   The tag sits at the trailing edge so tags line up as a column. */
.session-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 46px;
  padding: 1px 2px 1px 8px;
  border-radius: 10px;
  color: var(--text-dim);
  font-weight: 500;
  transition: background 0.12s ease;
}

.session-item + .session-item {
  margin-top: 1px;
}

.session-item.active,
.session-item.menu-open {
  background: var(--bg-input);
}

.session-lead {
  flex: none;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
}

.session-item .hash {
  font-size: 17px;
  line-height: 1;
  color: var(--text-dim);
  opacity: 0.7;
}

.session-body {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
}

.session-item .name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 15px;
  line-height: 1.25;
  color: var(--text);
  -webkit-user-select: none; /* iOS Safari still requires the prefix */
  user-select: none;
  -webkit-touch-callout: none;
}

.session-item.active .name,
.session-item.unread .name {
  color: var(--text-bright);
  font-weight: 600;
}

.session-body .provider-badge {
  flex: none;
  padding: 0 5px;
  font-size: 9.5px;
  line-height: 15px;
}

.session-item .del {
  display: grid;
  place-items: center;
  flex: none;
  width: 36px;
  height: 44px;
  margin-left: -8px; /* sits flush against the dashboard toggle */
  padding: 0;
  background: transparent;
  color: var(--text-dim);
  opacity: 0.5;
}

.session-item .del svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.session-item .del:hover:not(:disabled),
.session-item .del:active:not(:disabled) {
  opacity: 1;
  color: var(--danger);
  background: transparent;
}

.session-name-edit {
  flex: 1;
  min-width: 0;
  width: 100%;
  padding: 3px 6px;
  background: var(--bg-dark);
  border: 1px solid var(--accent);
  border-radius: 5px;
  color: var(--text-bright);
  font: inherit;
  font-size: 16px; /* keep iOS from zooming */
  outline: none;
}

.session-item .unread-dot {
  width: 9px;
  height: 9px;
  flex: none;
  border-radius: 50%;
  background: var(--tool);
  box-shadow: 0 0 0 2px rgb(59 165 93 / 0.25);
}

/* Spinner = working now. Distinct shape from the unread dot on purpose: the
   two states must be tellable apart at a glance, not only by motion. */
.session-item .work-spinner {
  width: 13px;
  height: 13px;
  flex: none;
  border-radius: 50%;
  border: 2px solid rgb(255 255 255 / 0.18);
  border-top-color: var(--accent);
  animation: spin 0.7s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .session-item .work-spinner {
    animation-duration: 2.4s;
  }
}

.menu-scrim.session-row-scrim {
  z-index: 56;
}

.menu-pop.session-row-menu {
  z-index: 57;
  min-width: 200px;
  max-width: min(280px, calc(100vw - 16px));
  transform-origin: top left;
}

.session-row-menu-title {
  padding: 6px 10px 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-dim);
}

.drawer-foot {
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--bg-dark);
  flex: none;
}

.drawer-settings-entry {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  min-height: 44px;
  text-align: left;
}

.drawer-settings-entry svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.text-btn {
  background: none;
  border: 0;
  padding: 0;
  color: var(--text-dim);
  font-size: 14px;
}

.empty-note {
  padding: 24px 16px;
  color: var(--text-dim);
  font-size: 14px;
  text-align: center;
}

/* ── messages ────────────────────────────────────────────────────────── */

.messages {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  /* Explicit, and not optional: with only overflow-y set, overflow-x computes
     to `auto` rather than `visible`, so one wide table turned the whole
     transcript into a horizontally draggable pane and pushed the messages off
     screen. Wide content scrolls inside its own box (.table-wrap, pre). */
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  -webkit-touch-callout: none;
  padding: 12px 0 4px;
  will-change: scroll-position;
  transform: translateZ(0);
  overscroll-behavior-y: contain;
}

/* Browser anchoring must not silently follow new content when the setting is OFF.
   Older-history prepends retain their explicit viewport re-anchoring. */
#messages[data-auto-scroll='off'] {
  overflow-anchor: none;
}

/* The submitted turn owns at least one visible transcript viewport. Its
   non-semantic reservation shrinks as the actual response grows underneath. */
#messages[data-prompt-turn='active'] {
  padding-bottom: calc(4px + var(--prompt-turn-space, 0px));
  overflow-anchor: none;
}

.msg {
  display: block;
  padding: 6px 14px;
}

.msg.msg-user {
  display: flex;
  gap: 10px;
  padding: 6px 14px;
}

/* Hover only where there is one: iOS keeps :hover on the last tapped message,
   and a tap on the transcript is the full-screen switch. */
@media (hover: hover) {
  .msg:hover {
    background: rgb(0 0 0 / 0.04);
  }
}

.avatar {
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 14px;
  font-weight: 700;
  color: #fff;
  background: var(--accent);
}

.msg-body {
  width: 100%;
  min-width: 0;
}

.msg.msg-user .msg-body {
  flex: 1;
}

/* A flex/grid child defaults to min-width:auto, refusing to shrink below its
   content's intrinsic width — which is how a table's full width leaks out to
   the ancestors even though .table-wrap scrolls. */
.msg-text {
  min-width: 0;
  overflow-wrap: anywhere;
}

.msg-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 2px;
}

.msg-author {
  font-weight: 600;
  color: var(--text-bright);
  font-size: 14px;
}

.msg-time {
  color: var(--text-dim);
  font-size: 11px;
}

.msg-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.msg-text code {
  background: var(--bg-dark);
  padding: 1px 4px;
  border-radius: 4px;
  font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  font-size: 13px;
}

.msg-text code,
.event-body code {
  cursor: copy;
  touch-action: manipulation;
}

.msg-text code:active,
.event-body code:active {
  opacity: 0.72;
}

.msg-text pre,
.event-body pre {
  max-width: 100%;
  margin: 6px 0;
  padding: 10px 12px;
  background: var(--bg-dark);
  border: 1px solid var(--border);
  border-radius: 6px;
  /* Long tool output must scroll inside its own box; the page itself never
     scrolls sideways on a phone. */
  overflow-x: auto;
  white-space: pre;
  overflow-wrap: normal;
  -webkit-overflow-scrolling: touch;
}

/* Historical replies skip animation, but must not flash diagram source. */
.mermaid-wrap[data-render-pending] {
  visibility: hidden;
}

.mermaid-wrap {
  margin: 12px 0;
  background: var(--bg-dark);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}

.mermaid-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 10px;
  background: rgba(0, 0, 0, 0.25);
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  color: var(--text-dim);
  user-select: none;
}

.mermaid-title {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  font-weight: 600;
  color: var(--text-bright);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mermaid-actions {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-shrink: 0;
}

.mermaid-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 5px;
  color: var(--text);
  font-size: 11px;
  line-height: 1.2;
  white-space: nowrap;
  flex-shrink: 0;
  cursor: pointer;
  touch-action: manipulation;
  transition:
    background 0.15s ease,
    color 0.15s ease;
}

.mermaid-btn:hover,
.mermaid-btn:active {
  background: var(--bg-hover, rgba(255, 255, 255, 0.1));
  color: var(--text-bright);
}

.mermaid-scroll {
  padding: 12px;
  overflow-x: auto;
  overflow-y: auto;
  max-height: min(46dvh, 380px);
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x pan-y;
  display: flex;
  justify-content: flex-start;
  user-select: none;
}

.mermaid-wrap.is-collapsed .mermaid-header {
  border-bottom: 0;
}

.mermaid-wrap.is-collapsed .mermaid-scroll[hidden] {
  display: none !important;
}

.mermaid-chart {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  min-width: 0;
  margin: 0 auto;
  transform-origin: 0 0;
  will-change: transform;
}

.mermaid-wrap[data-diagram-type='gantt'] .mermaid-chart {
  display: inline-block;
  width: auto;
  min-width: max-content;
}

.mermaid-chart svg {
  display: block;
  width: 100%;
  max-width: 100%;
  max-height: min(42dvh, 340px);
  height: auto;
  margin: 0 auto;
  cursor: grab;
  fill: initial;
  stroke: initial;
  stroke-width: initial;
  stroke-linecap: initial;
  stroke-linejoin: initial;
}

.mermaid-wrap[data-diagram-type='gantt'] .mermaid-chart svg {
  width: auto;
  min-width: 100%;
  max-width: none;
  max-height: none;
}

/* ── mermaid modal fullscreen ── */
.mermaid-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal[hidden] {
  display: none !important;
}

.mermaid-modal[data-motion] .mm-modal-content {
  will-change: transform, opacity;
}

/* On its way out it takes no more taps. */
.mermaid-modal[data-motion='closing'] {
  pointer-events: none;
}

.mm-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
  backdrop-filter: blur(8px);
}

.mm-modal-content {
  position: relative;
  z-index: 1;
  /* The opening and closing scale it onto the figure it came from. */
  transform-origin: 0 0;
  width: 94vw;
  height: 90dvh;
  max-width: 1200px;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
}

.mm-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 14px;
  background: var(--bg-dark);
  border-bottom: 1px solid var(--border);
}

.mm-modal-title {
  font-weight: 600;
  font-size: 14px;
  color: var(--text-bright);
}

.mm-modal-tools {
  display: flex;
  align-items: center;
  gap: 6px;
}

.mm-modal-body {
  flex: 1;
  overflow: hidden;
  touch-action: none;
  padding: 24px;
  display: grid;
  place-items: center;
  user-select: none;
}

/* Every move of the stage is a transform: written straight from the finger,
   or played as Web Animations keyframes (DESIGN.md "Viewer motion"). A CSS
   transition here would trail the finger. */
.mm-modal-stage {
  transform-origin: 0 0;
  display: inline-block;
  will-change: transform;
}

.mm-modal-stage svg {
  display: block;
  width: auto;
  max-width: none;
  height: auto;
  cursor: grab;
  fill: initial;
  stroke: initial;
  stroke-width: initial;
  stroke-linecap: initial;
  stroke-linejoin: initial;
}

.msg-text pre code,
.event-body pre code {
  display: block;
  width: max-content;
  min-width: 100%;
  background: none;
  padding: 0;
  color: #c9d1d9;
  font-size: 12.5px;
  line-height: 1.5;
}

/* highlight.js token palette, tuned for piweb's dark transcript. The library
   supplies only semantic spans; keeping the colours here avoids a second theme
   stylesheet and makes message and tool-event code blocks match. */
.hljs-comment,
.hljs-quote {
  color: #8b949e;
  font-style: italic;
}

.hljs-keyword,
.hljs-selector-tag,
.hljs-literal,
.hljs-type,
.hljs-doctag {
  color: #ff7b72;
}

.hljs-title,
.hljs-title.class_,
.hljs-title.function_,
.hljs-section {
  color: #d2a8ff;
}

.hljs-string,
.hljs-regexp,
.hljs-template-tag,
.hljs-template-variable {
  color: #a5d6ff;
}

.hljs-number,
.hljs-symbol,
.hljs-bullet,
.hljs-link {
  color: #79c0ff;
}

.hljs-attr,
.hljs-attribute,
.hljs-variable,
.hljs-property,
.hljs-params {
  color: #ffa657;
}

.hljs-built_in,
.hljs-builtin-name,
.hljs-meta {
  color: #f2cc60;
}

.hljs-tag,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
  color: #7ee787;
}

.hljs-emphasis {
  font-style: italic;
}

.hljs-strong {
  font-weight: 700;
}

.hljs-addition {
  color: #aff5b4;
  background: rgb(46 160 67 / 0.16);
}

.hljs-deletion {
  color: #ffdcd7;
  background: rgb(248 81 73 / 0.16);
}

.msg-files {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 6px;
}

.msg-files img {
  max-width: min(100%, 320px);
  max-height: 320px;
  border-radius: 8px;
  display: block;
}

.msg-inline-media {
  margin: 8px 0;
}

.msg-inline-media img,
.msg-text img {
  max-width: min(100%, 320px);
  max-height: 320px;
  border-radius: 8px;
  display: block;
  cursor: zoom-in;
}

.msg-inline-media .video-file {
  margin: 4px 0;
}

.video-file {
  width: min(100%, 320px);
  max-width: min(100%, 320px);
  overflow: hidden;
  border-radius: 8px;
  background: var(--bg-alt);
}

.video-file video {
  display: block;
  width: 100%;
  max-height: 320px;
  background: #000;
}

.video-download {
  width: 100%;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px 12px;
  border: 0;
  border-top: 1px solid var(--border);
  background: transparent;
  color: #00a8fc;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  touch-action: manipulation;
}

.video-download:active,
.video-download:focus-visible {
  background: var(--bg-input);
  color: var(--text-bright);
  outline: none;
}

.file-link {
  display: inline-block;
  padding: 8px 12px;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: #00a8fc;
  text-decoration: none;
  font-size: 14px;
}

/* ── streamed events (thinking / tools) ──────────────────────────────── */

@keyframes popInSlideUp {
  0% {
    opacity: 0;
    transform: translateY(14px);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

.event {
  margin: 4px 14px;
  border-radius: 8px;
  background: var(--bg-alt);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-left: 3px solid var(--border);
  overflow: hidden;
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.event.pop-in,
.msg.pop-in {
  animation: popInSlideUp 0.22s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  will-change: transform, opacity;
}

.event.thinking {
  border-left-color: var(--thinking);
  background: rgba(167, 139, 250, 0.05);
}

.event.tool {
  border-left-color: var(--tool);
  background: rgba(99, 102, 241, 0.05);
}

.event.tool_result {
  border-left-color: #10b981;
  background: rgba(16, 185, 129, 0.05);
}

.tool-progress {
  margin: 0 14px 0 26px;
  padding: 1px 10px;
  border-left: 2px solid var(--tool);
  color: var(--text-dim);
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.event.system {
  border-left-color: var(--accent);
}

.event.error {
  border-left-color: var(--danger);
  background: rgba(239, 68, 68, 0.08);
}

.event summary {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  cursor: pointer;
  color: var(--text-dim);
  font-size: 13px;
  list-style: none;
  user-select: none;
  -webkit-user-select: none;
  transition:
    color 0.15s ease,
    background-color 0.15s ease;
}

.event summary:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.03);
}

.event summary::-webkit-details-marker {
  display: none;
}

.event-chevron {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text-dim);
  transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
  transform: rotate(0deg);
}

.event[open] > summary .event-chevron {
  transform: rotate(90deg);
}

.event summary .label {
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.event summary .peek {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  font-size: 12px;
  opacity: 0.75;
}

.event[open] summary .peek {
  display: none;
}

/* Live clock on the tool call still in flight. Stays visible when the row is
   expanded — that is exactly when the reader is asking "is this thing alive?" */
.event summary .event-elapsed {
  flex: none;
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  padding: 1px 7px;
  border-radius: 999px;
  color: var(--accent, #5865f2);
  background: rgb(88 101 242 / 0.14);
}

.event summary .event-elapsed::before {
  content: '⏳ ';
}

@media (prefers-reduced-motion: no-preference) {
  .event summary .event-elapsed {
    animation: toolElapsedPulse 2s ease-in-out infinite;
  }

  @keyframes toolElapsedPulse {
    0%,
    100% {
      opacity: 1;
    }
    50% {
      opacity: 0.55;
    }
  }
}

.event-body-wrap {
  display: grid;
  grid-template-rows: 1fr;
}

.event .event-body {
  min-height: 0;
  padding: 0 12px 10px;
  font-size: 13px;
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 380px;
  overflow-y: auto;
  opacity: 1;
}

/* System/command output is short and worth reading without a tap. */
.event.system .event-body,
.event.error .event-body {
  padding-top: 2px;
}

/* /claude-usage, /gpt-usage, /agy-usage as a card (public/usage-report.js):
   one meter per window, no emoji lights or text bars (DESIGN.md "Usage reports"). */
.event.usage .event-body {
  padding-bottom: 12px;
  white-space: normal;
  max-height: none;
}

.usage-report {
  display: grid;
  gap: 12px;
}

.usage-report :is(p, h4) {
  margin: 0;
}

.usage-report .usage-caption,
.usage-report .usage-note,
.usage-report .usage-footer {
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-dim);
}

.usage-report .usage-note.is-warn {
  color: #f28b8d;
}

:root[data-theme='light'] .usage-report .usage-note.is-warn {
  color: var(--danger);
}

.usage-report .usage-footer {
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.usage-group {
  display: grid;
  gap: 14px;
}

.usage-report .usage-group-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-2);
}

.usage-row {
  display: grid;
  gap: 6px;
}

.usage-row-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.usage-label {
  min-width: 0;
  font-size: 15px;
  font-weight: 500;
  color: var(--text-bright);
}

.usage-sub {
  margin-left: 6px;
  font-size: 12px;
  font-weight: 400;
  color: var(--text-dim);
}

.usage-value {
  flex: none;
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  font-variant-numeric: tabular-nums;
}

.usage-value-label {
  font-size: 12px;
  color: var(--text-dim);
}

.usage-value strong {
  font-size: 15px;
  font-weight: 650;
  color: var(--text-bright);
}

/* The upload progress track's shape; --border so the empty part shows on both themes. */
.usage-track {
  height: 5px;
  overflow: hidden;
  border-radius: 999px;
  background: var(--border);
}

.usage-fill {
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
}

.usage-row[data-level='high'] .usage-fill {
  background: var(--danger);
}

.usage-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 2px 12px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

.usage-row.is-disabled .usage-label {
  color: var(--text-dim);
}

/* BTW reuses the existing composer. Its small ownership card replaces the
   main-only typing indicator while the side transcript is selected. */
.messages[hidden], .btw-card[hidden] { display: none !important; }
.main.btw-sliding { overflow: hidden; }
.btw-swipe-ready .messages { touch-action: pan-y; }
.btw-context-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
.app.btw-active .typing { display: none !important; }
/* Jump to present belongs to the main transcript; it must not float over BTW. */
.app.btw-active .jump-live { display: none !important; }
/* BTW header: same surface as the topbar (no extra slab), icon actions only,
   16px visual gutter via the 44px targets, one-line metadata. */
.btw-card {
  display: flex;
  align-items: center;
  gap: 4px;
  min-height: 52px;
  padding: 0 max(4px, env(safe-area-inset-right)) 0 max(4px, env(safe-area-inset-left));
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  color: var(--text);
  flex: none;
}
.btw-card .icon-btn { width: 44px; height: 44px; }
.btw-card .icon-btn:disabled { opacity: .45; }
.btw-card .icon-btn:focus-visible { outline: 2px solid var(--text); outline-offset: -2px; }
.btw-card > div { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.btw-card strong { font-size: 15px; font-weight: 650; line-height: 1.25; color: var(--text); }
.btw-card span { color: var(--text-2); font-size: 12px; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.btw-card span.btw-live::before {
  content: '';
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: 6px;
  border-radius: 50%;
  background: var(--accent);
  vertical-align: 1px;
}
/* Main view while a BTW answer is pending: one quiet link on the typing line. */
/* The prompt of the turn being read, pinned under the header: one line folded,
   the whole prompt when tapped. Overlays the transcript so it never shifts it,
   and the next prompt's row pushes it up out of this clip like a sticky header. */
.prompt-pin-clip {
  position: absolute;
  left: 0;
  right: 0;
  z-index: 5;
  overflow: hidden;
  padding-bottom: 12px; /* room for the bar's shadow */
  pointer-events: none;
  /* Own compositing layer: iOS can otherwise paint the momentum-scrolling
     transcript over this overlay, so the text below shows through. */
  will-change: transform;
  transform: translateZ(0);
}
.prompt-pin {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
  min-height: 44px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  will-change: transform;
  transform: translateZ(0);
  /* A grey surface, set apart from the transcript under it: --bg-hover's tint
     over the opaque --bg (the tint is translucent; alone, the transcript would
     show through). */
  background: linear-gradient(var(--bg-hover), var(--bg-hover)), var(--bg);
  color: var(--text-2);
  font-size: 14px;
  line-height: 20px;
  text-align: left;
  cursor: pointer;
  pointer-events: auto;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  box-shadow: 0 6px 12px -10px rgba(0, 0, 0, 0.6);
}
/* Held down, a second tint layer, still over the opaque --bg. */
.prompt-pin:active { background: linear-gradient(var(--bg-hover), var(--bg-hover)), linear-gradient(var(--bg-hover), var(--bg-hover)), var(--bg); }
.prompt-pin-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.prompt-pin.expanded .prompt-pin-text {
  max-height: min(40dvh, 320px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  user-select: text;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--text);
}
.prompt-pin-chevron { flex: none; display: flex; width: 20px; height: 20px; color: var(--text-dim); transition: transform 160ms ease; }
.prompt-pin-chevron svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.prompt-pin.expanded .prompt-pin-chevron { transform: rotate(180deg); }
.prompt-pin.pushed { box-shadow: none; }
@media (prefers-reduced-motion: reduce) { .prompt-pin-chevron { transition: none; } }
.app.btw-active .prompt-pin-clip { display: none !important; }
.btw-pending-row { display: flex; justify-content: flex-end; height: 25px; padding: 0 8px; flex: none; pointer-events: none; }
.typing:not([hidden]) + .btw-pending-row { margin-top: -27px; }
.btw-pending-row button {
  position: relative;
  pointer-events: auto;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  min-width: 44px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-2);
  font: inherit;
  font-size: 12px;
}
.btw-pending-row button:active { background: var(--bg-hover); }
/* The row is 25px so it can share the typing line; the touch target is 44px. */
.btw-pending-row button::after { content: ''; position: absolute; inset: -10px 0 -9px; }
.btw-waiting {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 16px 8px;
  color: var(--text-dim);
  font-size: 13px;
}
.btw-waiting .dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--text-dim);
  animation: blink 1.2s infinite ease-in-out;
}
.btw-waiting .dot:nth-child(2) { animation-delay: 0.2s; }
.btw-waiting .dot:nth-child(3) { animation-delay: 0.4s; }
.btw-waiting span:last-child { margin-left: 6px; }
@media (prefers-reduced-motion: reduce) {
  .btw-waiting .dot { animation: none; opacity: 0.7; }
}

/* ── typing indicator ────────────────────────────────────────────────── */

.typing {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 2px 16px 6px;
  color: var(--text-dim);
  font-size: 13px;
  flex: none;
}

.typing .dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--text-dim);
  animation: blink 1.2s infinite ease-in-out;
}

.typing .dot:nth-child(2) {
  animation-delay: 0.2s;
}

.typing .dot:nth-child(3) {
  animation-delay: 0.4s;
}

.typing-label {
  margin-left: 6px;
}

@keyframes blink {
  0%,
  80%,
  100% {
    opacity: 0.3;
  }
  40% {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .typing .dot {
    animation: none;
    opacity: 0.7;
  }
  .drawer {
    transition: none;
  }
}

/* ── transcript selection & custom handles ────────────────────────────── */

.messages .msg-text,
.messages .event-body,
.messages .msg-text *,
.messages .event-body * {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

@media (hover: hover) and (pointer: fine) {
  .messages .msg-text,
  .messages .event-body,
  .messages .msg-text *,
  .messages .event-body * {
    -webkit-user-select: text;
    user-select: text;
  }
}

/* Touch devices ship the custom transcript selection above, so no part of the
   shell may start a native selection: iOS otherwise anchors one outside the
   (user-select:none) transcript and expands it across the entire document. */
@media (hover: none) and (pointer: coarse) {
  body {
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
  }

  input,
  textarea,
  [contenteditable='true'] {
    -webkit-user-select: text;
    user-select: text;
    -webkit-touch-callout: default;
  }
}

::selection {
  color: inherit;
  background: rgb(88 101 242 / 0.48);
}

/* The selection bubble (.sel-bubble) paints the tint; a second one here would
   show through it as hard per-line boxes. */
::highlight(piweb-selection) {
  color: inherit;
  background-color: transparent;
}

.custom-selection-overlay {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 35;
}

.custom-selection-overlay.active {
  pointer-events: none;
}

.custom-selection-overlay.dragging .sel-backdrop {
  pointer-events: none !important;
}

.sel-backdrop {
  position: fixed;
  inset: 0;
  z-index: 33;
  background: transparent;
  pointer-events: auto;
}

.sel-highlight-boxes {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 34;
}

.sel-bubble-layer {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.22s ease;
}

.sel-bubble-layer.leaving {
  opacity: 0;
}

/* One translucent bubble around the selection: a light top, a deeper base,
   a bright rim and a soft glow, like a soap bubble resting on the text. */
.sel-bubble {
  stroke-width: 1.25;
  stroke-linejoin: round;
  filter: drop-shadow(0 6px 16px rgb(88 101 242 / 0.3));
}

.sel-bubble-fill-top { stop-color: rgb(186 196 255 / 0.34); }
.sel-bubble-fill-bottom { stop-color: rgb(99 102 241 / 0.24); }
.sel-bubble-rim-top { stop-color: rgb(255 255 255 / 0.7); }
.sel-bubble-rim-bottom { stop-color: rgb(165 180 252 / 0.38); }

/* ── iOS-style sleek teardrop selection handles ────────────────────────── */

.sel-handle {
  position: fixed;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  touch-action: none;
  z-index: 36;
  pointer-events: auto;
  user-select: none;
  -webkit-user-select: none;
}

.sel-handle-line {
  position: absolute;
  width: 2.5px;
  height: 100%;
  max-height: 20px;
  background: var(--accent);
  border-radius: 2px;
  box-shadow: 0 0 6px rgba(99, 102, 241, 0.5);
}

.sel-handle-pin,
.sel-handle-knob {
  position: absolute;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid #ffffff;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
  transition:
    transform 0.15s cubic-bezier(0.16, 1, 0.3, 1),
    background-color 0.15s ease;
}

.sel-handle-start {
  margin-left: -22px;
  margin-top: -22px;
}

.sel-handle-start .sel-handle-line {
  top: 12px;
}

.sel-handle-start .sel-handle-pin,
.sel-handle-start .sel-handle-knob {
  top: 4px;
}

.sel-handle-end {
  margin-left: -22px;
  margin-top: -22px;
}

.sel-handle-end .sel-handle-line {
  bottom: 12px;
}

.sel-handle-end .sel-handle-pin,
.sel-handle-end .sel-handle-knob {
  bottom: 4px;
}

.sel-handle:active .sel-handle-pin,
.sel-handle:active .sel-handle-knob,
.sel-handle.dragging .sel-handle-pin,
.sel-handle.dragging .sel-handle-knob {
  transform: scale(1.35);
  background: var(--accent-light);
  box-shadow: 0 0 10px var(--accent);
}

/* ── Floating action bubble ───────────────────────────────────────────── */

.selection-actions {
  position: fixed;
  z-index: 42;
  display: flex;
  align-items: center;
  overflow: hidden;
  transform: translateX(-50%);
  transform-origin: 50% 0;
  border: 1px solid rgb(255 255 255 / 0.32);
  border-radius: 999px;
  background:
    radial-gradient(120% 90% at 30% 0%, rgb(255 255 255 / 0.22), transparent 60%),
    rgb(52 54 78 / 0.5);
  box-shadow:
    inset 0 1px 1px rgb(255 255 255 / 0.35),
    inset 0 -6px 14px rgb(129 140 248 / 0.18),
    0 12px 30px rgb(0 0 0 / 0.35);
  backdrop-filter: blur(18px) saturate(180%);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  animation: actionsPop 0.5s linear both;
  padding: 3px;
  gap: 2px;
}

/* A bubble blowing up: overshoot, a small rebound, settle. */
@keyframes actionsPop {
  0% {
    opacity: 0;
    transform: translateX(-50%) translateY(-6px) scale(0.55);
  }
  30% {
    opacity: 1;
    transform: translateX(-50%) translateY(0) scale(1.08);
  }
  55% {
    transform: translateX(-50%) scale(0.97);
  }
  78% {
    transform: translateX(-50%) scale(1.01);
  }
  100% {
    opacity: 1;
    transform: translateX(-50%) scale(1);
  }
}

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

  .sel-bubble-layer {
    transition: none;
  }
}

.selection-actions button {
  min-width: 64px;
  height: 44px;
  padding: 0 16px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: #ffffff;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.2px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  touch-action: manipulation;
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}

.selection-actions button:active {
  background: rgba(255, 255, 255, 0.16);
}

.selection-actions button:disabled {
  color: rgba(255, 255, 255, 0.35);
}

/* ── composer ────────────────────────────────────────────────────────── */

.composer-wrap {
  position: relative;
  z-index: 40;
  flex: none;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 0 10px calc(10px + env(safe-area-inset-bottom));
  background: var(--bg);
}

.composer {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  padding: 4px;
  background: var(--bg-input);
  border-radius: 10px;
}

.composer textarea {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  padding: 9px 4px;
  max-height: 40vh;
  resize: none;
  line-height: 1.4;
}

/* One line, like the pane composers: an empty box's scrollHeight counts its
   placeholder, and a long voice-input status (語音輸入：<error>) grew the box
   to three lines that stayed after the status went (iPhone, 2026-10-27). */
.composer textarea::placeholder {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Komorebi voice input (voice-ime.js). The mic sits before Send; on, it is
   accent. While listening the box's edge shows what the microphone hears: a
   1–2px ring on the edge (inset, so a pane composer's overflow: hidden cannot
   cut it) and a soft glow outside the box, both following the level
   (--voice-level, 0–1). Nothing is drawn over the text: an inner glow did,
   once quiet iPhone speech moved it (2026-11-01, 「不要擋到我輸入的內容」).
   While nothing is heard the ring breathes, so the box still reads as
   listening. */
.voice-ime-btn.voice-on {
  color: var(--accent);
}

.voice-ime-btn.voice-error {
  color: var(--danger);
}

/* Listening, but the clean-up model is down: sentences go in as heard. Amber,
   not red: voice input itself works (voiceStatus `warn`). */
.voice-ime-btn.voice-warn:not(.voice-error) {
  color: #d9902f;
}

.composer.voice-listening:has(.voice-ime-btn.voice-warn) {
  --voice-ring: color-mix(in srgb, #d9902f calc(55% + var(--voice-level, 0) * 45%), transparent);
}

@media (pointer: coarse) {
  .voice-ime-btn {
    min-width: 44px;
    min-height: 44px;
  }
}

.composer.voice-listening {
  --voice-ring: color-mix(in srgb, var(--accent) calc(45% + var(--voice-level, 0) * 55%), transparent);
  box-shadow:
    inset 0 0 0 calc(1px + var(--voice-level, 0) * 1px) var(--voice-ring),
    0 0 calc(2px + var(--voice-level, 0) * 10px) calc(var(--voice-level, 0) * 2px)
      color-mix(in srgb, var(--accent) calc(var(--voice-level, 0) * 55%), transparent);
  transition: box-shadow 120ms linear;
}

.composer.voice-listening.voice-quiet {
  animation: voice-ring-breathe 1.6s ease-in-out infinite;
}

@keyframes voice-ring-breathe {
  50% {
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 12%, transparent);
  }
}

.composer.voice-listening.voice-hearing {
  --voice-ring: color-mix(in srgb, var(--accent) calc(70% + var(--voice-level, 0) * 30%), transparent);
}

/* While it listens the mic shows what it hears: five bars, the newest reading
   in the middle and the two before it spreading out (--h, 0.17–1, from
   voice-ime.js at ~10 Hz). Speech moves them; while nothing is heard they are
   dots that breathe, so an open but quiet mic still reads as listening. */
.voice-meter {
  display: none;
  align-items: center;
  gap: 2px;
  height: 20px;
}

.voice-ime-btn.voice-live svg {
  display: none;
}

.voice-ime-btn.voice-live .voice-meter {
  display: flex;
}

.voice-meter i {
  width: 3px;
  height: 20px;
  border-radius: 999px;
  background: currentColor;
  transform: scaleY(var(--h, 0.17));
  transition: transform 90ms linear;
}

.voice-ime-btn.voice-live.voice-quiet .voice-meter {
  animation: voice-breathe 1.6s ease-in-out infinite;
}

@keyframes voice-breathe {
  50% {
    opacity: 0.45;
  }
}

/* Before and after the clean-up (as on the Komorebi IME page): the box's text
   is drawn again in .voice-mirror, exactly over it, while a sentence is being
   heard or a rewrite has just landed; the box's own text goes transparent but
   keeps its caret. A sentence being spoken is dim and underlined with a
   blinking caret; heard, it reads as text with a dotted underline until the
   rewrite lands; what the
   rewrite put in is highlighted and fades out, what it took out is struck
   through in --danger, fades and folds away. */
.voice-ime-frame {
  position: relative;
}

.voice-mirror {
  position: absolute;
  z-index: 1;
  margin: 0;
  overflow: hidden;
  border-style: solid;
  border-color: transparent;
  background: transparent;
  color: var(--text-bright);
  white-space: pre-wrap;
  overflow-wrap: break-word;
  pointer-events: none;
}

/* Framed, so it outranks a composer's own color (.dashboard-pane-composer
   textarea loads later at the same specificity and left the box's text
   showing under the layer: the rewrite read as doubled glyphs, iPad 2026-10-27). */
.voice-ime-frame textarea.voice-mirrored {
  color: transparent;
  caret-color: var(--text-bright);
}

/* Voice input on (no keyboard): a sideways stroke on the box circles words
   (the lasso in voice-ime.js), so only vertical panning is the browser's. */
.voice-ime-frame textarea[inputmode='none'] {
  touch-action: pan-y;
}

/* The circle is the quote selection's bubble (.sel-bubble), over the box and
   under its 刪除/取消 pill (.selection-actions, z-index 42). */
.voice-lasso-layer {
  position: fixed;
  inset: 0;
  z-index: 41;
  pointer-events: none;
}

.voice-mirror .voice-fresh {
  border-radius: 3px;
  background: color-mix(in srgb, var(--accent) 34%, transparent);
  color: inherit;
  transition: background 1.2s ease;
}

.voice-mirror .voice-fresh.settled {
  background: transparent;
}

.voice-mirror .voice-gone {
  color: color-mix(in srgb, var(--danger) 80%, transparent);
  text-decoration: line-through 1.5px;
  font-size: 1em;
  opacity: 1;
  transition:
    opacity 1.2s ease,
    font-size 0.5s ease 0.7s;
}

/* Fades, then folds its width away so the kept text closes up smoothly. */
.voice-mirror .voice-gone.settled {
  font-size: 0;
  opacity: 0;
}

.voice-preedit {
  color: var(--text-dim);
  text-decoration: underline 1.5px color-mix(in srgb, var(--accent) 60%, transparent);
  text-underline-offset: 4px;
}

.voice-preedit.listening::after {
  content: '';
  display: inline-block;
  width: 2px;
  height: 1.05em;
  margin-left: 2px;
  vertical-align: -0.15em;
  background: var(--accent);
  animation: voice-caret 1s steps(2) infinite;
}

/* Heard, waiting for the clean-up: it already reads as text (the clean-up
   can take seconds on a busy host), only a dotted underline says it may still
   change. */
.voice-preedit.refining {
  color: inherit;
  text-decoration: underline dotted 1.5px color-mix(in srgb, var(--accent) 55%, transparent);
}

@keyframes voice-caret {
  50% {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .composer.voice-listening,
  .voice-mirror .voice-fresh {
    transition: none;
  }

  .voice-preedit.listening::after,
  .voice-ime-btn.voice-live.voice-quiet .voice-meter,
  .composer.voice-listening.voice-quiet {
    animation: none;
  }

  .voice-meter i {
    transition: none;
  }

  /* No fade: the changes stay marked until they are taken away. */
  .voice-mirror .voice-fresh.settled {
    background: color-mix(in srgb, var(--accent) 34%, transparent);
  }

  .voice-mirror .voice-gone,
  .voice-mirror .voice-gone.settled {
    transition: none;
    font-size: 1em;
    opacity: 1;
  }
}

/* Life quick tags: one tap prefixes the next message with a 【label】 card. */
.life-tags {
  display: none;
  gap: 8px;
  margin: 0 4px 8px;
  overflow-x: auto;
  scrollbar-width: none;
}

.life-tags::-webkit-scrollbar {
  display: none;
}

.app.life-mode .life-tags {
  display: flex;
}

/* Hot keywords: the same chips in standard sessions (Life keeps its own tags
   in this spot); more than fit scroll sideways. */
.hot-keywords {
  display: flex;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}

.app.life-mode .hot-keywords,
.app.btw-active .hot-keywords {
  display: none;
}

.life-tag {
  flex: none;
  padding: 5px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-alt);
  color: var(--text-2);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

.life-tag[aria-pressed='true'] {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
}

.quote-preview {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 4px 8px;
  padding: 6px 6px 6px 12px;
  border-left: 3px solid var(--accent);
  border-radius: 8px;
  background: var(--bg-alt);
  color: var(--text-dim);
  font-size: 13px;
  animation: quoteBannerIn 0.16s cubic-bezier(0.16, 1, 0.3, 1);
  user-select: none;
  -webkit-user-select: none;
}

@keyframes quoteBannerIn {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.quote-preview-label {
  flex: none;
  color: var(--accent-light);
  font-weight: 700;
}

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

#quote-preview-remove {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--text-dim);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition:
    background-color 0.15s ease,
    color 0.15s ease,
    transform 0.1s ease;
}

#quote-preview-remove:hover,
#quote-preview-remove:active {
  background: rgba(255, 255, 255, 0.14);
  color: var(--text-bright);
  transform: scale(1.08);
}

.attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 8px 4px;
}

.image-compression-options {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 8px;
  padding: 0 4px 6px;
  color: var(--text-2);
  font-size: 12px;
}

.image-compression-options label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  cursor: pointer;
  color: var(--text);
  font-size: 13px;
}

.image-compression-options input {
  width: 20px;
  height: 20px;
  margin: 0;
  accent-color: var(--accent);
}

.image-compression-options input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

#image-compression-hint {
  flex: 1 1 180px;
  min-width: 0;
}

.upload-progress {
  padding: 8px 4px 6px;
  color: var(--text-dim);
  font-size: 12px;
}

.upload-progress-head {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 5px;
}

.upload-progress-track {
  height: 5px;
  overflow: hidden;
  border-radius: 999px;
  background: var(--bg-alt);
}

.upload-progress-bar {
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
  transition: width 120ms ease-out;
}

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

.chip {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 8px;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 13px;
  color: var(--text);
  max-width: 100%;
}

/* Image attachments show as a small square thumbnail with the remove button
   floated on the corner, so several fit across the composer. */
.chip.chip-image {
  position: relative;
  width: 52px;
  height: 52px;
  padding: 0;
  overflow: hidden;
}

.chip-thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.chip.chip-image .chip-remove {
  position: absolute;
  top: 1px;
  right: 1px;
  width: 18px;
  height: 18px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgb(0 0 0 / 0.6);
  color: #fff;
  font-size: 13px;
  line-height: 1;
}

.chip-remove {
  background: none;
  border: 0;
  padding: 0;
  color: var(--text-dim);
  line-height: 1;
  font-size: 16px;
}

.chip .chip-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 160px;
}

.chip button {
  background: none;
  border: 0;
  padding: 0;
  color: var(--text-dim);
  line-height: 1;
  font-size: 16px;
}

/* ── slash autocomplete ──────────────────────────────────────────────── */

.autocomplete {
  /*
   * In normal flow, NOT absolutely positioned. Absolute + `max-height: 46vh`
   * overflowed off-screen with the keyboard open: `vh` tracks the layout
   * viewport, which iOS does not shrink for the keyboard, so the box became
   * taller than the visible area above the composer and got clipped at both
   * ends with nothing to scroll. As a flex item it is bounded by the column and
   * always scrolls internally instead.
   *
   * --ac-max is kept in sync with visualViewport by app.js (the keyboard only
   * changes the VISUAL viewport, which no CSS unit exposes).
   */
  flex: 0 1 auto;
  min-height: 0;
  max-height: var(--ac-max, 40dvh);
  margin-bottom: 6px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* Don't chain the scroll to the page when the list hits its end. */
  overscroll-behavior: contain;
  /* Let the browser own vertical panning; taps are detected in JS. */
  touch-action: pan-y;
  background: var(--bg-alt);
  border: 1px solid var(--bg-dark);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.45);
  z-index: 10;
}

.ac-head {
  /* Sticky so you can still see what the list is while scrolling it. */
  position: sticky;
  top: 0;
  background: var(--bg-alt);
  padding: 8px 12px;
  font-size: 12px;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  border-bottom: 1px solid var(--bg-dark);
}

.ac-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 9px 12px;
}

.ac-item.sel {
  background: var(--bg-input);
}

.ac-name {
  color: var(--text-bright);
  font-weight: 600;
  font-size: 14px;
}

.ac-name .arg {
  color: var(--text-dim);
  font-weight: 400;
}

.ac-desc {
  color: var(--text-dim);
  font-size: 12.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── wider screens ───────────────────────────────────────────────────── */

@media (min-width: 768px) and (min-height: 500px), (min-width: 768px) and (pointer: fine) {
  .drawer {
    position: relative;
    transform: none;
    touch-action: auto;
    width: 260px;
    max-width: none;
    border-right: 1px solid var(--bg-dark);
  }

  #btn-menu,
  .scrim {
    display: none;
  }

  .app.drawer-collapsed .drawer {
    display: none;
  }

  .app.drawer-collapsed #btn-menu {
    display: grid;
  }

  .messages {
    padding-inline: 8px;
  }
}

/* ── infinite scroll sentinel ─────────────────────────────────────────── */

.top-sentinel {
  padding: 12px 14px;
  text-align: center;
  color: var(--text-dim);
  font-size: 13px;
  cursor: pointer;
}

.top-sentinel.start {
  cursor: default;
  opacity: 0.6;
  font-size: 12px;
}

/* ── search ──────────────────────────────────────────────────────────── */

.search-panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Sits between the topbar and the transcript, taking at most half the
     screen so the conversation behind it stays visible for context. */
  max-height: 55dvh;
  background: var(--bg-alt);
  border-bottom: 1px solid var(--bg-dark);
  flex: none;
}

.search-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 8px 8px 12px;
  flex: none;
}

.search-bar input {
  flex: 1;
  min-width: 0;
  padding: 10px 12px;
  background: var(--bg-input);
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--text-bright);
  font-size: 16px; /* keep iOS from zooming */
  outline: none;
}

.search-bar input:focus {
  border-color: var(--accent);
}

.search-results {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  touch-action: pan-y;
  padding-bottom: 6px;
}

.search-hit {
  padding: 9px 14px;
  border-top: 1px solid var(--bg-dark);
}

.search-hit .hit-meta {
  display: flex;
  gap: 8px;
  align-items: baseline;
  color: var(--text-dim);
  font-size: 12px;
  margin-bottom: 2px;
}

.search-hit .hit-who {
  font-weight: 600;
  color: var(--text);
}

.search-hit .hit-text {
  font-size: 13.5px;
  color: var(--text);
  overflow-wrap: anywhere;
}

.search-hit mark {
  background: #b58900;
  color: #1e1f22;
  border-radius: 2px;
  padding: 0 1px;
}

.search-empty {
  padding: 16px;
  text-align: center;
  color: var(--text-dim);
  font-size: 13px;
}

/* Flash the message you jumped to, so it is obvious where you landed. */
.msg.jump-target,
.event.jump-target {
  animation: jump-flash 2s ease-out;
}

@keyframes jump-flash {
  0%,
  40% {
    background: rgb(88 101 242 / 0.28);
  }
  100% {
    background: transparent;
  }
}

@media (prefers-reduced-motion: reduce) {
  .msg.jump-target,
  .event.jump-target {
    animation: none;
    outline: 2px solid var(--accent);
  }
}

.jump-live {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--composer-h, 84px) + 12px);
  z-index: 50;
  padding: 8px 14px;
  background: var(--accent);
  color: #fff;
  border: 0;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  box-shadow: 0 4px 12px rgb(0 0 0 / 0.4);
  transition:
    opacity 0.2s ease,
    transform 0.2s ease;
  opacity: 0;
  transform: translateX(-50%) translateY(8px);
  pointer-events: none;
  cursor: pointer;
}

.jump-live.visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}

/* ── full-screen reading ─────────────────────────────────────────────────
   A tap on the transcript (session-fullscreen.js): the header and the bottom
   chrome turn into overlays, so the transcript fills .main at once, then slide
   off (hidden only once they are gone, so nothing off screen takes focus).
   Leaving, they slide back over it before the layout returns. */
.app.session-fullscreen .main {
  padding-top: env(safe-area-inset-top);
}

/* The status bar keeps its band; the text scrolls under it, not under the clock. */
.app.session-fullscreen .main::before {
  content: '';
  position: absolute;
  z-index: 47;
  top: 0;
  left: 0;
  right: 0;
  height: env(safe-area-inset-top);
  background: var(--bg);
}

.app.session-fullscreen .main > .topbar,
.app.session-fullscreen #typing,
.app.session-fullscreen .main > .btw-pending-row,
.app.session-fullscreen #composer-wrap {
  position: absolute;
  z-index: 45;
  left: 0;
  right: 0;
  visibility: hidden;
  transition:
    transform 140ms ease-in,
    visibility 0s linear 140ms;
}

.app.session-fullscreen .main > .topbar {
  top: 0;
  transform: translateY(-100%);
}

/* The typing line and the BTW link share the line above the composer. */
.app.session-fullscreen #typing,
.app.session-fullscreen .main > .btw-pending-row {
  bottom: var(--composer-h, 84px);
  transform: translateY(calc(100% + var(--composer-h, 84px)));
}

.app.session-fullscreen #composer-wrap {
  bottom: 0;
  transform: translateY(100%);
}

.app.session-fullscreen.session-fullscreen-leaving .main > .topbar,
.app.session-fullscreen.session-fullscreen-leaving #typing,
.app.session-fullscreen.session-fullscreen-leaving .main > .btw-pending-row,
.app.session-fullscreen.session-fullscreen-leaving #composer-wrap {
  visibility: visible;
  transform: none;
  transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.app.session-fullscreen .main > .prompt-pin-clip,
.app.session-fullscreen #search-panel,
.app.session-fullscreen #deleted-banner {
  display: none !important;
}

.app.session-fullscreen #messages {
  padding-bottom: calc(4px + env(safe-area-inset-bottom));
}

.app.session-fullscreen #messages[data-prompt-turn='active'] {
  padding-bottom: calc(4px + env(safe-area-inset-bottom) + var(--prompt-turn-space, 0px));
}

.app.session-fullscreen .main > .stream-dock {
  padding-bottom: env(safe-area-inset-bottom);
}

.app.session-fullscreen #jump-live {
  bottom: calc(12px + env(safe-area-inset-bottom) + var(--stream-player-h, 0px));
}

/* session-fullscreen.js measures the layout it is about to return to. */
.app.session-fullscreen-measuring .main > .topbar,
.app.session-fullscreen-measuring #typing,
.app.session-fullscreen-measuring .main > .btw-pending-row,
.app.session-fullscreen-measuring #composer-wrap {
  transition: none !important;
}

@media (prefers-reduced-motion: reduce) {
  .app.session-fullscreen .main > .topbar,
  .app.session-fullscreen #typing,
  .app.session-fullscreen .main > .btw-pending-row,
  .app.session-fullscreen #composer-wrap {
    transition: none !important;
  }
}

/* ── rendered markdown ───────────────────────────────────────────────── */

.msg-text h1,
.msg-text h2,
.msg-text h3,
.msg-text h4,
.msg-text h5,
.msg-text h6 {
  margin: 12px 0 6px;
  line-height: 1.3;
  color: var(--text-bright);
  font-weight: 700;
}

.msg-text h1 {
  font-size: 1.35em;
}
.msg-text h2 {
  font-size: 1.22em;
}
.msg-text h3 {
  font-size: 1.1em;
}
.msg-text h4,
.msg-text h5,
.msg-text h6 {
  font-size: 1em;
}

.msg-text > *:first-child {
  margin-top: 0;
}
.msg-text > *:last-child {
  margin-bottom: 0;
}

.msg-text p {
  margin: 0 0 8px;
}

.msg-text ul,
.msg-text ol {
  margin: 4px 0 8px;
  padding-left: 22px;
}

.msg-text li {
  margin: 2px 0;
}

.msg-text blockquote {
  margin: 6px 0;
  padding: 2px 0 2px 10px;
  border-left: 3px solid var(--border);
  color: var(--text-dim);
}

.msg-text hr {
  margin: 12px 0;
  border: 0;
  border-top: 1px solid var(--border);
}

.msg-text a {
  color: #00a8fc;
  text-decoration: none;
  overflow-wrap: anywhere;
}

.msg-text a:hover {
  text-decoration: underline;
}

.msg-text strong {
  color: var(--text-bright);
}

/* A YouTube video link is a 44px touch target. The external player is loaded
   only after a real click, then stays inside the message at a mobile-safe size.
   Standard-width cards remain 16:9; min-height protects YouTube's 200px player
   requirement when user/event columns are narrower than 356px. */
.msg-text .youtube-inline-link,
.event-body .youtube-inline-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 44px;
  min-height: 44px;
  padding: 0 6px;
  border-radius: 7px;
  line-height: 1.25;
  text-decoration: none;
  touch-action: manipulation;
}

.youtube-inline-link::before {
  content: '▶';
  flex: none;
  color: #ff3355;
  font-size: 12px;
}

.youtube-inline-link:active,
.youtube-inline-link[aria-expanded='true'] {
  background: color-mix(in srgb, var(--accent) 13%, transparent);
}

.youtube-inline-player {
  width: min(100%, 640px);
  margin: 10px 0 12px;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: #101010;
  box-shadow: 0 10px 28px rgb(0 0 0 / 0.22);
}

.youtube-inline-frame {
  width: 100%;
  min-height: 200px;
  aspect-ratio: 16 / 9;
  background: #101010;
}

.youtube-inline-frame iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.youtube-inline-actions {
  min-height: 52px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 4px 6px;
  border-top: 1px solid rgb(255 255 255 / 0.12);
  background: color-mix(in srgb, var(--bg-alt) 94%, #101010);
}

.msg-text .youtube-inline-open,
.event-body .youtube-inline-open,
.youtube-inline-close {
  min-width: 44px;
  min-height: 44px;
  padding: 0 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text-bright);
  font: inherit;
  font-size: 13px;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
}

.msg-text .youtube-inline-open,
.event-body .youtube-inline-open {
  color: var(--accent-light);
}

.youtube-inline-open:active,
.youtube-inline-close:active {
  background: rgb(255 255 255 / 0.1);
}

/* Tables scroll inside their own box — the page must never scroll sideways. */
.table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 8px 0;
}

/* A tap opens the table full screen (app.js); pointer also lets iOS deliver the
   tap. No tap flash: the viewer itself is the feedback. */
.table-wrap table {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* The full-screen copy keeps the transcript's table look, but its cells may wrap
   (between words) so the table fits the viewer's width at full size. */
.mermaid-modal[data-kind='table'] .mm-modal-body {
  padding: 12px;
}

/* A table is a sheet, not a dark figure: one surface, the bar above it divided
   by a hairline rather than a grey band. */
.mermaid-modal[data-kind='table'] .mm-modal-head {
  background: var(--bg-alt);
}

.table-viewer-sheet {
  white-space: normal;
}

.msg-text.table-viewer-sheet th,
.msg-text.table-viewer-sheet td {
  white-space: normal;
  overflow-wrap: normal;
  padding: 9px 10px;
  line-height: 1.45;
}

/* There a value may wrap, so one long command does not starve the other
   columns. */
.msg-text.table-viewer-sheet td code {
  white-space: normal;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

/* In the viewer the sheet lifts off the card a little. */
.msg-text.table-viewer-sheet table {
  background: var(--bg);
}

/* A rounded frame with hairline rows and no column rules: the gaps between
   columns separate them, as in a native list. The header sits on a faint tint
   in a quieter voice, so the rows read first. */
.msg-text table {
  border-collapse: separate;
  border-spacing: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 13.5px;
  font-variant-numeric: tabular-nums;
}

.msg-text th,
.msg-text td {
  padding: 7px 12px;
  text-align: left;
  vertical-align: top;
  white-space: nowrap;
}

.msg-text td {
  border-top: 1px solid var(--border);
}

.msg-text th {
  background: color-mix(in srgb, var(--text) 5%, transparent);
  color: var(--text-2);
  font-size: 12.5px;
  font-weight: 600;
  vertical-align: bottom;
}

/* The tinted header follows the frame's rounded corners. */
.msg-text thead tr:first-child th:first-child {
  border-top-left-radius: calc(var(--radius) - 1px);
}

.msg-text thead tr:first-child th:last-child {
  border-top-right-radius: calc(var(--radius) - 1px);
}

/* A command or value in a cell stays in one piece (no break after a hyphen). */
.msg-text td code {
  white-space: nowrap;
  font-size: 12.5px;
}

/* ── math ────────────────────────────────────────────────────────────── */

.math-display {
  margin: 10px 0;
  /* A long equation scrolls in place rather than widening the page. */
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 2px;
}

.katex {
  font-size: 1.05em;
}

/* KaTeX ships light-theme colours; inherit the app's instead. */
.katex,
.katex .mord,
.katex .mbin,
.katex .mrel,
.katex .mopen,
.katex .mclose,
.katex .mpunct {
  color: inherit;
}

/* ── settings ───────────────────────────────────────────────────────── */

.settings-dialog {
  width: min(100%, 620px);
  height: 100dvh;
  max-width: 620px;
  max-height: 100dvh;
  margin: 0 auto;
  padding: 0;
  border: 0;
  background: var(--bg);
  color: var(--text);
}

/* Enter/exit and the right-drag back are driven by settings-motion.js
   (WAAPI), so an exit can animate before the dialog closes. While a finger
   drags the page, --settings-reveal (0→1) lightens the dimming behind it. */
.settings-dialog::backdrop {
  background: rgb(0 0 0 / 0.58);
  opacity: calc(1 - var(--settings-reveal, 0));
}

.settings-dialog[data-motion] {
  will-change: transform;
}

.settings-page {
  height: 100%;
  display: flex;
  flex-direction: column;
  padding-top: env(safe-area-inset-top);
}

.settings-head {
  min-height: 58px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  border-bottom: 1px solid var(--border);
  flex: none;
}

.settings-head h2 {
  margin: 0;
  color: var(--text-bright);
  font-size: 19px;
  font-weight: 650;
}

/* A page's title takes the focus when it opens (autofocus, tabindex -1), so
   iOS does not ring the back or close button; Tab goes on to the controls. */
dialog h2[tabindex='-1']:focus {
  outline: none;
}

.settings-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 8px 16px calc(28px + env(safe-area-inset-bottom));
}

.settings-section {
  margin-top: 22px;
}

.settings-section h3 {
  margin: 0 0 8px 4px;
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.settings-group {
  overflow: hidden;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: 13px;
}

.settings-row {
  width: 100%;
  min-height: 54px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: left;
}

.settings-row:last-child {
  border-bottom: 0;
}

.settings-row-icon {
  width: 22px;
  height: 22px;
  flex: none;
  color: var(--text-dim);
}

.settings-row-icon svg {
  width: 100%;
  height: 100%;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.settings-row-label {
  flex: 1;
  min-width: 0;
}

.settings-value {
  color: var(--text-dim);
  font-size: 13px;
}

.settings-select {
  border: 0;
  border-radius: 8px;
  padding: 6px 0;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 14px;
  outline: none;
  cursor: pointer;
  max-width: 170px;
  /* The box is as wide as the longest option; the chosen one ends at the row's end. */
  text-align: right;
  text-align-last: right;
}

/* A setting that only applies while another is on (the clean-up API while
   整理草稿 is off): dimmed and not editable. */
.settings-row.settings-field.is-off {
  opacity: 0.45;
}

/* A text setting too long for the row's end (URLs): icon and label on the
   first line, the value as a second line under the label, no box of its own
   (the whole row is the tap target). */
.settings-row.settings-field {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr);
  align-items: center;
  column-gap: 12px;
  row-gap: 2px;
}

/* A model name is too long for the row's end: the select goes under the
   label like .settings-input, its chosen value read from the start. */
.settings-field .settings-select {
  grid-column: 2;
  width: 100%;
  max-width: none;
  min-width: 0;
  padding: 0;
  font-size: 16px; /* < 16px makes iOS zoom on focus */
  line-height: 1.35;
  text-align: left;
  text-align-last: left;
  text-overflow: ellipsis;
}

.settings-input {
  grid-column: 2;
  width: 100%;
  min-width: 0;
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 16px; /* < 16px makes iOS zoom on focus */
  line-height: 1.35;
  outline: none;
  text-overflow: ellipsis;
}

.settings-input:focus {
  color: var(--text-bright);
}

.settings-input::placeholder {
  color: var(--text-dim);
  opacity: 0.7;
}

/* Values are plain text at the row's end or under its label, like
   Appearance's, never a box inside the group. Editing shows on the row (a ring
   round a boxless value reads as a stray frame); the caret and brighter text
   mark the field itself. */
label.settings-row:focus-within {
  background: var(--bg-hover);
}


.settings-chevron {
  color: var(--text-dim);
  font-size: 24px;
  line-height: 1;
}

.notification-toggle,
.settings-toggle {
  position: relative;
  width: 44px;
  height: 26px;
  flex: none;
  border-radius: 999px;
  background: var(--bg-input);
  box-shadow: inset 0 0 0 1px var(--border);
  transition: background 160ms ease, box-shadow 160ms ease;
}

.notification-toggle::after,
.settings-toggle::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--text-dim);
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.28);
  transition: transform 180ms cubic-bezier(0.22, 1, 0.36, 1), background 160ms ease;
}

.notification-toggle[data-state='on'],
.settings-toggle[data-state='on'] {
  background: var(--accent);
  box-shadow: inset 0 0 0 1px transparent;
}

.notification-toggle[data-state='on']::after,
.settings-toggle[data-state='on']::after {
  transform: translateX(18px);
  background: #fff;
}

#btn-notify[data-notification-state='blocked'] .notification-toggle,
#btn-notify[data-notification-state='unsupported'] .notification-toggle,
#btn-notify[data-notification-state='home-screen'] .notification-toggle {
  opacity: 0.55;
}

.settings-account {
  padding: 14px;
  border-bottom: 1px solid var(--border);
  color: var(--text-dim);
  font-size: 14px;
}

.settings-danger {
  color: #f28b8d;
}

/* Subscriptions are rows of one group, like General: the provider's glyph
   where a row's icon goes, the name over its status, the action at the end. */
.subscription-card + .subscription-card {
  border-top: 1px solid var(--border);
}

.subscription-summary {
  min-height: 54px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 14px;
}

.subscription-logo {
  width: 22px;
  height: 22px;
  flex: none;
  display: grid;
  place-items: center;
  color: var(--text-dim);
  font-size: 20px;
  line-height: 1;
}

.subscription-summary > div:nth-child(2) {
  flex: 1;
  min-width: 0;
}

.subscription-summary strong,
.subscription-summary span {
  display: block;
}

.subscription-summary strong {
  color: var(--text);
  font-weight: 400;
}

.subscription-summary span {
  margin-top: 1px;
  color: var(--text-dim);
  font-size: 12px;
}

.subscription-action {
  min-height: 44px;
  /* The text lines up with the row's end, as a value or chevron does. */
  margin-right: -8px;
  padding: 0 8px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--accent);
  font-weight: 650;
}

/* Sign-in actions are borderless accent text (Connect, Open, Submit) and the
   device code is plain large text, tapped to copy. Hover fill only where a
   pointer hovers: iOS keeps :hover on the last tapped button. */
.subscription-action:active,
.subscription-open:active,
.subscription-code-submit:active,
.device-code:active {
  background: var(--bg-hover);
}

@media (hover: hover) {
  .subscription-action:hover,
  .subscription-open:hover,
  .subscription-code-submit:hover,
  .device-code:hover {
    background: var(--bg-hover);
  }
}

.subscription-action:disabled {
  opacity: 0.6;
}

.subscription-device {
  padding: 14px;
  border-top: 1px solid var(--border);
  text-align: center;
}

.subscription-device p {
  margin: 0 0 12px;
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.45;
}

.device-code {
  display: block;
  width: 100%;
  padding: 12px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--text-bright);
  font: 700 24px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.09em;
}

.subscription-open {
  display: grid;
  place-items: center;
  min-height: 44px;
  margin-top: 4px;
  background: transparent;
  color: var(--accent);
  font-weight: 650;
  text-decoration: none;
}

.subscription-logo-claude {
  color: #d97757;
}

.subscription-logo-agy {
  color: #4285f4;
}

.subscription-code-form {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}

.subscription-code-input {
  flex: 1;
  min-width: 0;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--bg-input);
  color: var(--text-bright);
  /* 16px keeps iOS from zooming the page when the field is focused. */
  font: 16px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace;
}

.subscription-code-input:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

.subscription-code-submit {
  flex: none;
  min-height: 44px;
  background: transparent;
  color: var(--accent);
  font-weight: 650;
}

.subscription-code-submit:disabled {
  opacity: 0.6;
}

.subscription-progress {
  display: block;
  margin-top: 10px;
  color: var(--text-dim);
  font-size: 12px;
}

/* A pasted code that failed, a stalled worker, or a failed sign-in. */
.subscription-progress.is-error,
.subscription-card .is-error {
  color: var(--danger);
}

.settings-footnote {
  margin: 8px 4px 0;
  color: var(--text-dim);
  font-size: 12px;
  line-height: 1.4;
}

@media (min-width: 700px) {
  .settings-dialog {
    height: min(760px, calc(100dvh - 48px));
    margin: auto;
    border: 1px solid var(--border);
    border-radius: 16px;
    box-shadow: 0 24px 80px rgb(0 0 0 / 0.55);
  }
}

@media (prefers-reduced-motion: reduce) {
  #trash-sheet .trash-panel,
  .notification-toggle,
  .notification-toggle::after,
  .settings-toggle,
  .settings-toggle::after {
    animation: none;
    transition: none;
  }
}

/* ── recently deleted ────────────────────────────────────────────────── */

.trash-entry {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 6px 0;
  margin-bottom: 6px;
}

.trash-entry svg {
  width: 16px;
  height: 16px;
}

.trash-count {
  margin-left: auto;
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--bg-input);
  color: var(--text);
  font-size: 12px;
  text-align: center;
}

/* Modal surface shared by compact utility sheets and the Recently deleted page. */
.sheet {
  position: fixed;
  inset: 0;
  /* Sheets can be launched from the drawer, so they sit above it (55) while
     remaining below the dedicated image lightbox (60). */
  z-index: 58;
  display: flex;
  align-items: flex-end;
  background: rgb(0 0 0 / 0.55);
}

dialog.sheet {
  width: auto;
  height: auto;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  color: inherit;
}

dialog.sheet::backdrop {
  background: transparent;
}

.sheet-panel {
  width: 100%;
  /* --sheet-max is synced to visualViewport by app.js: with the keyboard open
     dvh still reports the full screen, so a dvh-only cap runs off-screen. */
  max-height: var(--sheet-max, 82dvh);
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--bg-alt);
  border-radius: 14px 14px 0 0;
  padding-bottom: env(safe-area-inset-bottom);
}

/* Recently deleted is a destination, not a half-height action sheet. Keep the
   Settings page underneath and slide this full-height page over it. */
#trash-sheet {
  position: absolute;
  align-items: stretch;
  background: transparent;
  overflow: hidden;
}

#trash-sheet .trash-panel {
  width: 100%;
  height: 100%;
  max-height: 100%;
  margin: 0;
  padding-top: env(safe-area-inset-top);
  border-radius: 0;
  background: var(--bg);
  animation: trash-page-enter 240ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

@keyframes trash-page-enter {
  from {
    transform: translateX(100%);
  }
  to {
    transform: translateX(0);
  }
}

.sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 8px 12px 16px;
  border-bottom: 1px solid var(--bg-dark);
  flex: none;
}

.sheet-title {
  font-weight: 700;
  color: var(--text-bright);
}

.sheet-head-actions {
  display: flex;
  align-items: center;
  gap: 4px;
}

#trash-sheet .sheet-head {
  min-height: 58px;
  justify-content: flex-start;
  gap: 8px;
  padding: 0 12px;
  border-bottom-color: var(--border);
}

#trash-sheet .sheet-title {
  flex: 1;
  font-size: 19px;
  font-weight: 650;
}

.trash-panel .icon-btn {
  width: 44px;
  height: 44px;
}

.sheet-note {
  margin: 0;
  padding: 10px 16px 0;
  color: var(--text-dim);
  font-size: 12.5px;
  flex: none;
}

.trash-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 8px 0 12px;
}

.trash-item {
  padding: 8px 16px 10px;
  border-top: 1px solid var(--bg-dark);
  transition: background 120ms ease;
}

.trash-item.selected {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}

.trash-item-main {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

.trash-item.selecting .trash-item-main {
  cursor: pointer;
}

.trash-item-copy {
  min-width: 0;
  flex: 1;
}

.trash-select-input {
  width: 22px;
  height: 22px;
  margin: 0;
  accent-color: var(--accent);
  flex: none;
}

.trash-item .t-name {
  color: var(--text-bright);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.trash-item .t-meta {
  color: var(--text-dim);
  font-size: 12px;
  margin-top: 2px;
}

.trash-actions {
  display: flex;
  gap: 8px;
  margin-top: 6px;
  flex-wrap: wrap;
}

.trash-actions button,
.trash-select-toggle,
.trash-bulk-bar button {
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-input);
  color: var(--text);
  font: inherit;
  font-size: 13px;
}

.trash-select-toggle {
  min-width: 64px;
  border-color: transparent;
  background: transparent;
  color: var(--accent);
  font-weight: 650;
}

.trash-actions .primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-weight: 600;
}

.trash-actions .danger,
.trash-bulk-bar .trash-bulk-danger {
  color: #fb7f7f;
  border-color: #5c2b2b;
}

.trash-bulk-bar {
  flex: none;
  padding: 10px 16px max(10px, env(safe-area-inset-bottom));
  border-top: 1px solid var(--bg-dark);
  background: var(--bg-alt);
}

#btn-trash-delete-all {
  width: 100%;
}

.trash-selection-tools {
  display: grid;
  grid-template-columns: minmax(84px, auto) 1fr minmax(118px, auto);
  align-items: center;
  gap: 8px;
}

#trash-selected-count {
  color: var(--text-dim);
  font-size: 12.5px;
  text-align: center;
  white-space: nowrap;
}

.trash-bulk-bar button:disabled,
.trash-actions button:disabled,
.trash-select-toggle:disabled {
  opacity: 0.45;
}

@media (min-width: 768px) {
  #trash-sheet .trash-panel {
    padding-top: 0;
    padding-bottom: 0;
    border-radius: 15px;
  }

  @keyframes trash-page-enter {
    from {
      opacity: 0;
      transform: translateY(10px) scale(0.985);
    }
    to {
      opacity: 1;
      transform: translateY(0) scale(1);
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  #trash-sheet .trash-panel {
    animation: none;
  }
}

.deleted-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  background: #4a3b12;
  color: #f5e3b3;
  font-size: 13px;
  flex: none;
}

.deleted-banner button {
  margin-left: auto;
  padding: 5px 10px;
  border: 0;
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
}

/* ── image lightbox ──────────────────────────────────────────────────── */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  grid-template-rows: auto 1fr auto;
  background: #0b0b0d;
  /* The surface owns its gestures: sideways pages, down dismisses. */
  touch-action: none;
  user-select: none;
  animation: lb-in 0.16s ease-out;
}

@keyframes lb-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.lb-bar {
  grid-row: 1;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: calc(10px + env(safe-area-inset-top)) 10px 10px 12px;
  color: #fff;
}

.lb-count {
  flex: none;
  padding-left: 4px;
  color: rgb(255 255 255 / 0.72);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.lb-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 2px;
}

/* Every toolbar control is the same borderless 44px icon target. */
.lb-bar .icon-btn {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  color: rgb(255 255 255 / 0.9);
  transition: background 0.15s ease;
}

.lb-bar .icon-btn svg {
  width: 22px;
  height: 22px;
}

@media (hover: hover) {
  .lb-bar .icon-btn:hover {
    background: rgb(255 255 255 / 0.1);
  }
}

.lb-bar .icon-btn:active {
  background: rgb(255 255 255 / 0.18);
}

.lb-stage {
  grid-row: 2;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px 12px;
  overflow: hidden;
}

/*
 * Small images are scaled UP to a comfortable size rather than left as a stamp
 * in a black void — an icon shown at its native 180px on a phone reads as a
 * mistake. `contain` keeps the aspect ratio, and the frame gives a transparent
 * or light-backgrounded image something to sit on.
 */
#lb-img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 14px;
  box-shadow: 0 18px 50px rgb(0 0 0 / 0.6);
  background: rgb(255 255 255 / 0.03);
  will-change: transform, opacity;
}

/* Zoom baked into layout (see setLightboxCommitted): sized inline, may overflow the stage. */
#lb-img.zoomed {
  flex: none;
  max-width: none;
  max-height: none;
}

/* Square-ish small images (icons) get a sensible target size. */
#lb-img.fit-up {
  width: min(88vw, 62vh, 560px);
}

.lb-nav {
  position: absolute;
  z-index: 2;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: 50%;
  background: rgb(255 255 255 / 0.12);
  color: #fff;
  backdrop-filter: blur(6px);
}

.lb-nav[disabled] {
  opacity: 0.2;
}

.lb-prev {
  left: 10px;
}
.lb-next {
  right: 10px;
}

/* ── filmstrip ── */

.lb-strip {
  grid-row: 3;
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 10px 12px calc(12px + env(safe-area-inset-bottom));
  overflow-x: auto;
  overscroll-behavior: contain;
  touch-action: pan-x;
  scrollbar-width: none;
}

.lb-strip::-webkit-scrollbar {
  display: none;
}

.lb-thumb {
  position: relative;
  flex: none;
  width: 52px;
  height: 52px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.08);
  overflow: hidden;
  opacity: 0.6;
  display: grid;
  place-items: center;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-dim);
  transition:
    opacity 0.15s ease,
    border-color 0.15s ease,
    background 0.15s ease;
  user-select: none;
}

.lb-thumb-num {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-dim);
  pointer-events: none;
}

.lb-thumb img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.lb-thumb img.loaded {
  opacity: 1;
}

.lb-thumb.active {
  opacity: 1;
  border-color: var(--accent);
  background: rgba(255, 255, 255, 0.16);
}

/* One image: the strip and the arrows are just clutter. */
.lightbox.single .lb-strip,
.lightbox.single .lb-nav {
  display: none;
}

@media (max-width: 767px) {
  .lb-nav {
    display: none;
  }
}

.msg-files img,
.msg-inline-media img,
.msg-text img {
  cursor: zoom-in;
}

/* ── header command shortcuts ────────────────────────────────────────── */

.icon-btn.danger {
  color: #f87171;
}

/* Five controls plus the session name is tight on a 390px phone; shaving the
   hit target to 34px keeps the name readable while staying above the ~32px
   comfortable-touch floor. Up to 480px, so a Pro Max (430–440px) is no
   tighter than a 390px phone. */
.menu-item.phone-only {
  display: none;
}

@media (max-width: 480px) {
  .topbar .icon-btn {
    width: 34px;
    height: 34px;
  }

  .topbar {
    gap: 2px;
  }
}

/* Status and usage move into the ⋯ menu whenever the chat column is 600px or
   narrower, or a busy session (stop shown) squeezes the name to one letter.
   Phones up to 600px wide, and 768–860px with the drawer pinned beside it
   (260px). The cut used to be 420px: a 430–440px Pro Max kept both icons and
   its name came out 12px wide (2026-10-26). */
@media (max-width: 600px) {
  .app:not(.life-mode) .topbar #btn-status,
  .app:not(.life-mode) .topbar #btn-gpt-usage {
    display: none;
  }

  .app:not(.life-mode) .menu-item.phone-only:not([hidden]) {
    display: flex;
  }
}

@media (min-width: 768px) and (max-width: 860px) and (min-height: 500px),
  (min-width: 768px) and (max-width: 860px) and (pointer: fine) {
  .app:not(.life-mode):not(.drawer-collapsed) .topbar #btn-status,
  .app:not(.life-mode):not(.drawer-collapsed) .topbar #btn-gpt-usage {
    display: none;
  }

  .app:not(.life-mode):not(.drawer-collapsed) .menu-item.phone-only:not([hidden]) {
    display: flex;
  }
}

.model-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 6px 0 12px;
}

.model-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 11px 16px;
  border: 0;
  border-top: 1px solid var(--bg-dark);
  background: none;
  color: var(--text);
  text-align: left;
  font-size: 14px;
}

.model-item .m-ref {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.model-item .m-tag {
  flex: none;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--bg-input);
  color: var(--text-dim);
  font-size: 11px;
}

.model-item.current {
  color: var(--text-bright);
  font-weight: 600;
}

.model-item.current::before {
  content: '✓';
  color: var(--accent);
}

.model-item:not(.current)::before {
  content: '';
  width: 1ch;
}

/* Thinking is a per-session setting. The bulb stays muted for the runtime
   default/off and becomes progressively brighter as the selected effort rises. */
#btn-thinking {
  position: relative;
}

#btn-thinking[data-level='minimal'],
#btn-thinking[data-level='low'] {
  color: #a5b4fc;
}

#btn-thinking[data-level='medium'],
#btn-thinking[data-level='high'],
#btn-thinking[data-level='xhigh'],
#btn-thinking[data-level='max'] {
  color: var(--thinking);
}

#btn-thinking[data-level='high']::after,
#btn-thinking[data-level='xhigh']::after,
#btn-thinking[data-level='max']::after {
  content: '';
  position: absolute;
  right: 5px;
  bottom: 5px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--thinking);
  box-shadow: 0 0 6px var(--thinking);
}

.thinking-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 6px 0 12px;
}

.thinking-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 58px;
  padding: 9px 16px;
  border: 0;
  border-top: 1px solid var(--bg-dark);
  background: none;
  color: var(--text);
  text-align: left;
}

.thinking-item::before {
  content: '';
  width: 9px;
  height: 9px;
  flex: none;
  border: 2px solid var(--border);
  border-radius: 50%;
}

.thinking-item.current {
  background: rgb(167 139 250 / 0.08);
  color: var(--text-bright);
}

.thinking-item.current::before {
  border-color: var(--thinking);
  background: var(--thinking);
  box-shadow: 0 0 0 3px rgb(167 139 250 / 0.16);
}

.thinking-copy {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.thinking-name {
  font-weight: 650;
  text-transform: capitalize;
}

.thinking-description {
  color: var(--text-dim);
  font-size: 12px;
}

.thinking-description.blocked-desc {
  color: var(--text-dim);
  font-style: italic;
}

.thinking-item[disabled],
.thinking-item.blocked {
  opacity: 0.42;
  cursor: not-allowed;
}

.thinking-item[disabled]::before,
.thinking-item.blocked::before {
  border-color: var(--border);
  background: transparent;
  box-shadow: none;
}

.thinking-item-pill {
  margin-left: auto;
  font-size: 11px;
  font-weight: 600;
  padding: 2px 7px;
  border-radius: 4px;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.thinking-item-pill.blocked {
  background: rgb(255 255 255 / 0.06);
  border: 1px solid var(--border);
  color: var(--text-dim);
}

:root[data-theme='light'] .thinking-item-pill.blocked {
  background: rgb(0 0 0 / 0.04);
  border-color: var(--border);
  color: var(--text-dim);
}

/* ── provider badge ──────────────────────────────────────────────────── */

.provider-badge {
  flex: none;
  padding: 1px 6px;
  border-radius: 5px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1.6;
  border: 1px solid transparent;
  white-space: nowrap;
}

/* Colour-coded by where the compute happens: green = this machine,
   NVIDIA green = NIM, and so on. Text label carries the meaning; colour is
   only a shortcut, so it still reads without colour vision. */
.provider-badge.local {
  background: rgb(59 165 93 / 0.18);
  border-color: rgb(59 165 93 / 0.5);
  color: #7ee2a8;
}

.provider-badge.nv {
  background: rgb(118 185 0 / 0.18);
  border-color: rgb(118 185 0 / 0.5);
  color: #a6e34d;
}

.provider-badge.gpt {
  background: rgb(16 163 127 / 0.18);
  border-color: rgb(16 163 127 / 0.5);
  color: #5fd3b8;
}

/* Codex variants — star / earth / sun / moon, each its own colour so they
   read apart at a glance (label still carries the meaning without colour). */
.provider-badge.astra {
  background: rgb(236 72 153 / 0.2);
  border-color: rgb(236 72 153 / 0.55);
  color: #f472b6;
}

/* Terracotta (clay/earth) — deliberately NOT green, so it reads apart from the
   LOCAL and GPT greens sitting right next to it in the drawer. */
.provider-badge.terra {
  background: rgb(224 122 95 / 0.2);
  border-color: rgb(224 122 95 / 0.55);
  color: #eeab97;
}

.provider-badge.sol {
  background: rgb(251 191 36 / 0.18);
  border-color: rgb(251 191 36 / 0.5);
  color: #fcd34d;
}

.provider-badge.luna {
  background: rgb(148 163 184 / 0.2);
  border-color: rgb(148 163 184 / 0.55);
  color: #cbd5e1;
}

.provider-badge.gem {
  background: rgb(66 133 244 / 0.18);
  border-color: rgb(66 133 244 / 0.5);
  color: #8ab4f8;
}

.provider-badge.kimi {
  background: rgb(139 92 246 / 0.18);
  border-color: rgb(139 92 246 / 0.5);
  color: #c4b5fd;
}

/* Claude's warm coral stays visually separate from the green local/GPT,
   blue Gemini, and violet OpenRouter badges around it. */
.provider-badge.claude,
.provider-badge.opus,
.provider-badge.sonnet,
.provider-badge.haiku,
.provider-badge.fable {
  background: rgb(217 119 87 / 0.2);
  border-color: rgb(217 119 87 / 0.58);
  color: #f0a58c;
}

.provider-badge.xai {
  background: rgb(255 255 255 / 0.1);
  border-color: rgb(255 255 255 / 0.28);
  color: #e6e6e6;
}

.provider-badge.or {
  background: rgb(139 92 246 / 0.18);
  border-color: rgb(139 92 246 / 0.5);
  color: #c4b5fd;
}

.provider-badge.sak,
.provider-badge.other {
  background: var(--bg-input);
  border-color: var(--border);
  color: var(--text-dim);
}

#header-badge {
  margin-left: 2px;
}

/* ── sheet search ────────────────────────────────────────────────────── */

.sheet-search {
  flex: none;
  padding: 10px 16px 4px;
}

.sheet-search input {
  width: 100%;
  padding: 10px 12px;
  background: var(--bg-input);
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--text-bright);
  font-size: 16px; /* keep iOS from zooming on focus */
  outline: none;
}

.sheet-search input:focus {
  border-color: var(--accent);
}

.model-item .provider-badge {
  margin-left: 2px;
}

.model-item mark {
  background: rgb(88 101 242 / 0.35);
  color: inherit;
  border-radius: 2px;
}

/* ── drawer preference toggles ───────────────────────────────────────── */

.notif-toggle,
.drawer-theme-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 6px 0;
  margin-bottom: 6px;
  text-align: left;
}

.notif-toggle svg,
.drawer-theme-toggle svg {
  width: 16px;
  height: 16px;
  flex: none;
}

.notif-state {
  margin-left: auto;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--bg-input);
  color: var(--text-dim);
  font-size: 11px;
}

.notif-state.on {
  background: rgb(59 165 93 / 0.2);
  color: #7ee2a8;
}

.notif-state.blocked {
  background: rgb(218 55 60 / 0.18);
  color: #f4a3a5;
}

/* ── toast ───────────────────────────────────────────────────────────── */

.toast {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(84px + env(safe-area-inset-bottom));
  z-index: 70;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: calc(100vw - 32px);
  padding: 10px 14px;
  border-radius: 10px;
  background: #101114;
  border: 1px solid var(--border);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.5);
  color: var(--text-bright);
  font-size: 13.5px;
  animation: toast-in 0.14s ease-out;
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translate(-50%, 8px);
  }
  to {
    opacity: 1;
    transform: translate(-50%, 0);
  }
}

.toast span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.toast button {
  flex: none;
  padding: 5px 11px;
  border: 0;
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
}

/* A tapped link copies rather than navigating, so it should not look like a
   plain hyperlink invitation to leave the page. */
.msg-text a {
  text-decoration-style: dotted;
  text-decoration-line: underline;
  text-underline-offset: 2px;
}

/* ── login: remember me ──────────────────────────────────────────────── */

.login-remember {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-dim);
  font-size: 13px;
  cursor: pointer;
  user-select: none;
}

.login-remember input {
  width: 16px;
  height: 16px;
  flex: none;
  accent-color: var(--accent);
}

/* ── overflow menu (⋯ in the topbar) ─────────────────────────────────────
   Anchored below the topbar on the right rather than being a bottom sheet:
   these are quick, low-commitment actions, and a sheet would be as heavy as
   the model picker. Sits above the topbar (z 45) but below the lightbox (60)
   and toasts (70). */
.menu-scrim {
  position: fixed;
  inset: 0;
  z-index: 44;
  /* Invisible: its only job is to catch the tap that dismisses the menu. */
  background: transparent;
}

.menu-pop {
  position: fixed;
  top: calc(var(--topbar-h) + env(safe-area-inset-top) + 4px);
  right: 8px;
  z-index: 45;
  min-width: 232px;
  max-width: calc(100vw - 16px);
  padding: 6px;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 10px 28px rgb(0 0 0 / 0.5);
  transform-origin: top right;
  animation: menu-in 110ms ease-out;
}

@keyframes menu-in {
  from {
    opacity: 0;
    transform: scale(0.96) translateY(-4px);
  }
}

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

.menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  /* The menu enters at scale(.96); 46px keeps the transformed touch target
     above the 44px mobile minimum for the entire animation. */
  min-height: 46px;
  padding: 10px 10px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 15px;
  text-align: left;
  cursor: pointer;
}

.menu-item svg {
  flex: none;
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.mi-label {
  flex: 1;
  min-width: 0;
}

/* The slash command the item runs, so the menu also teaches the typed form. */
.mi-cmd {
  flex: none;
  color: var(--text-dim);
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.menu-item:hover,
.menu-item:focus-visible {
  background: var(--bg-input);
}

.menu-item.danger {
  color: #f28b8d;
}

.menu-item.danger:hover,
.menu-item.danger:focus-visible {
  background: rgb(218 55 60 / 0.16);
}

.menu-item[disabled] {
  opacity: 0.4;
  cursor: default;
}

.menu-sep {
  height: 1px;
  margin: 6px 4px;
  background: var(--border);
}

.theme-icon-dark,
.settings-row-icon .theme-icon-dark {
  display: none;
}

:root[data-theme='light'] .theme-icon-light {
  display: none;
}

:root[data-theme='light'] .theme-icon-dark,
:root[data-theme='light'] .settings-row-icon .theme-icon-dark {
  display: block;
}

/* ── media gallery ──────────────────────────────────────────────────────────
 *
 * Every image/video in the session as square tiles, Discord-style. Square is
 * the point: a mixed grid of native aspect ratios reads as clutter, and the
 * tile is a target to open the real thing, not a place to judge composition.
 */
.media-grid {
  display: grid;
  /* auto-fill, not auto-fit: tiles keep their size on a wide screen instead of
     stretching into rectangles and defeating the square. */
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 6px;
  padding: 0 12px 16px;
  overflow-y: auto;
}

.media-tile {
  position: relative;
  aspect-ratio: 1;
  border: 0;
  padding: 0;
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg-3, #2b2d31);
  cursor: pointer;
}

.media-tile img,
.media-tile video {
  width: 100%;
  height: 100%;
  /* cover crops to the square; the lightbox shows the whole frame. */
  object-fit: cover;
  display: block;
}

.media-tile:focus-visible {
  outline: 2px solid var(--accent, #5865f2);
  outline-offset: 2px;
}

/* Video and audio need to be tellable apart from a photo at a glance. */
.media-badge {
  position: absolute;
  right: 4px;
  bottom: 4px;
  padding: 1px 5px;
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.66);
  color: #fff;
  font-size: 10px;
  line-height: 1.6;
  letter-spacing: 0.04em;
  pointer-events: none;
}

.media-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: 6px;
  font-size: 10px;
  line-height: 1.35;
  color: var(--text-2, #b5bac1);
  word-break: break-all;
  text-align: center;
}

/* ── stream player ───────────────────────────────────────────────────────
   One player, two places. Docked: between the transcript and the composer;
   .stream-dock is a flex child of .main, so the transcript shrinks instead of
   being covered. Embedded: in place of the tapped poster/track, which is
   hidden while the player stands in for it. */
.stream-dock {
  flex: none;
  display: grid;
}

[data-stream-embedded='true'] {
  display: none !important;
}

.stream-player {
  position: relative;
  z-index: 41;
  flex: none;
  display: grid;
  gap: 2px;
  margin: 0 10px 6px;
  padding: 6px 6px 4px;
  border: 1px solid var(--bg-dark);
  border-radius: 14px;
  background: var(--bg-alt);
  box-shadow: 0 6px 20px rgb(0 0 0 / 0.18);
}

.stream-player[hidden] {
  display: none;
}

.stream-player[data-placement='inline'] {
  z-index: auto;
  flex: 1 1 100%;
  width: min(100%, 560px);
  max-width: min(100%, 560px);
  margin: 0;
  box-shadow: none;
}

.msg-inline-media .stream-player[data-placement='inline'] {
  margin: 4px 0;
}

/* While zooming in/out the animated height clips the content. */
.stream-player.sp-morphing {
  overflow: hidden;
}

.stream-player[data-placement='inline'] .sp-expand {
  display: none;
}

.stream-player[data-placement='inline'] .sp-info {
  cursor: default;
}

.sp-head {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.sp-stage {
  flex: none;
  width: 64px;
  height: 40px;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: 8px;
  background: #000;
}

.stream-player[data-type='audio'] .sp-stage {
  width: 40px;
  background: var(--accent);
  color: #fff;
}

.sp-video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.sp-video[hidden],
.sp-art[hidden],
.sp-audio {
  display: none;
}

.sp-art {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
}

.sp-art svg {
  width: 22px;
  height: 22px;
}

.sp-info {
  flex: 1;
  min-width: 0;
  display: grid;
  gap: 2px;
  min-height: 44px;
  align-content: center;
  padding: 0 2px;
  border: 0;
  background: transparent;
  color: var(--text-bright);
  text-align: left;
  font: inherit;
  touch-action: manipulation;
}

.sp-title,
.sp-meta {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sp-title {
  font-size: 14px;
  font-weight: 600;
}

.sp-meta {
  color: var(--text-dim);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.stream-player[data-state='error'] .sp-meta {
  color: var(--danger, #f23f43);
}

.sp-controls {
  display: flex;
  align-items: center;
  flex: none;
}

.sp-btn {
  width: 40px;
  height: 44px;
  display: grid;
  place-items: center;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--text-bright);
  text-decoration: none;
  touch-action: manipulation;
}

.sp-btn svg {
  width: 22px;
  height: 22px;
}

.sp-toggle svg {
  width: 26px;
  height: 26px;
}

.sp-close {
  color: var(--text-dim);
}

.sp-btn:active,
.sp-btn:focus-visible,
.sp-info:focus-visible {
  background: var(--bg-hover);
  outline: none;
}

.sp-btn:disabled {
  opacity: 0.35;
}

.sp-btn[hidden] {
  display: none;
}

.sp-download[data-ready='true'] {
  box-shadow: inset 0 0 0 2px var(--accent);
}

.sp-download[aria-busy='true'] svg {
  animation: media-download-pulse 0.8s ease-in-out infinite alternate;
}

@keyframes media-download-pulse {
  to {
    opacity: 0.35;
    transform: translateY(2px);
  }
}

/* Played | buffered | not yet loaded, drawn on the native range track. */
.sp-seek {
  --sp-played: 0%;
  --sp-buffered: 0%;
  width: 100%;
  height: 20px;
  margin: 0;
  background: transparent;
  appearance: none;
  -webkit-appearance: none;
  touch-action: none;
}

.sp-seek::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(
    to right,
    var(--accent) 0 var(--sp-played),
    color-mix(in srgb, var(--text-dim) 55%, transparent) var(--sp-played) var(--sp-buffered),
    var(--bg-dark) var(--sp-buffered) 100%
  );
}

.sp-seek::-moz-range-track {
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(
    to right,
    var(--accent) 0 var(--sp-played),
    color-mix(in srgb, var(--text-dim) 55%, transparent) var(--sp-played) var(--sp-buffered),
    var(--bg-dark) var(--sp-buffered) 100%
  );
}

.sp-seek::-webkit-slider-thumb {
  width: 14px;
  height: 14px;
  margin-top: -5px;
  border-radius: 50%;
  background: var(--accent);
  -webkit-appearance: none;
}

.sp-seek::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border: 0;
  border-radius: 50%;
  background: var(--accent);
}

.sp-seek:disabled::-webkit-slider-thumb {
  visibility: hidden;
}

.sp-extra {
  display: none;
}

.stream-player.expanded .sp-extra {
  display: flex;
  align-items: center;
  gap: 2px;
}

.sp-position {
  margin-right: auto;
  padding-left: 6px;
  color: var(--text-dim);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.stream-player.expanded[data-type='video'] .sp-head {
  flex-wrap: wrap;
}

.stream-player.expanded[data-type='video'] .sp-stage {
  order: -1;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  max-height: 42dvh;
}

.stream-player.expanded[data-type='video'] .sp-video {
  object-fit: contain;
}

/* Keep "Jump to present" above the dock (its height is published by app.js). */
.app.has-stream-player .jump-live {
  bottom: calc(var(--composer-h, 84px) + 12px + var(--stream-player-h, 0px));
}

/* Audio attachments: a track row that plays in the dock. */
.audio-track {
  display: flex;
  align-items: center;
  gap: 10px;
  width: min(100%, 320px);
  min-height: 56px;
  padding: 8px 12px 8px 8px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-alt);
  color: var(--text-bright);
  font: inherit;
  text-align: left;
  touch-action: manipulation;
}

.audio-track:active,
.audio-track:focus-visible {
  background: var(--bg-input);
  outline: none;
}

.audio-track-glyph {
  width: 40px;
  height: 40px;
  flex: none;
  border-radius: 50%;
  background: var(--accent) no-repeat 56% 50% / 18px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M7 4.5v15l13-7.5z'/%3E%3C/svg%3E");
}

.audio-track[data-stream-playing='true'] .audio-track-glyph {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M6.5 5h4v14h-4zM13.5 5h4v14h-4z'/%3E%3C/svg%3E");
  background-position: 50% 50%;
}

.audio-track-text {
  display: grid;
  min-width: 0;
  gap: 2px;
}

.audio-track-name {
  overflow: hidden;
  font-size: 14px;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.audio-track-kind {
  color: var(--text-dim);
  font-size: 12px;
}

.audio-track.stream-current,
.media-tile.stream-current {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

.video-file.stream-current {
  box-shadow: 0 0 0 2px var(--accent);
}

.video-file[data-stream-playing='true'] .video-open::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M6.5 5h4v14h-4zM13.5 5h4v14h-4z'/%3E%3C/svg%3E");
  background-position: 50% 50%;
}

/* Inline reply videos have no download row under the poster. */
.stream-video .video-open {
  inset: 0;
}

.msg-inline-media .stream-video {
  display: block;
}

/* ── light appearance refinements ───────────────────────────────────────────
 * Most surfaces use the semantic tokens at the top of this file. These rules
 * cover components whose dark-mode translucency or fixed colour needs a
 * contrast-safe daytime equivalent. The image viewer and selection pill stay
 * intentionally dark so photos and native-style selection controls do not jump
 * between appearances. */

:root[data-theme='light'] .login-card {
  border: 1px solid var(--border);
  box-shadow: 0 16px 40px rgb(45 43 38 / 0.08);
}

:root[data-theme='light'] .session-item .work-spinner {
  border-color: rgb(45 43 38 / 0.14);
  border-top-color: var(--accent);
}

:root[data-theme='light'] .event {
  border-color: var(--border);
  border-left-color: var(--border);
  box-shadow: none;
}

:root[data-theme='light'] .event.thinking {
  background: #fbf9fc;
  border-left-color: var(--thinking);
}

:root[data-theme='light'] .event.tool {
  background: #f7faf8;
  border-left-color: var(--tool);
}

:root[data-theme='light'] .event.tool_result {
  background: #f7faf8;
  border-left-color: #397057;
}

:root[data-theme='light'] .event.system {
  background: var(--bg-alt);
  border-left-color: var(--accent);
}

:root[data-theme='light'] .event.error {
  background: #fff8f7;
  border-left-color: var(--danger);
}

:root[data-theme='light'] .event summary:hover,
:root[data-theme='light'] #quote-preview-remove:hover,
:root[data-theme='light'] #quote-preview-remove:active {
  background: rgb(45 43 38 / 0.045);
}

:root[data-theme='light'] .autocomplete,
:root[data-theme='light'] .menu-pop {
  box-shadow: 0 12px 32px rgb(45 43 38 / 0.1);
}

:root[data-theme='light'] .msg-text a,
:root[data-theme='light'] .video-download,
:root[data-theme='light'] .file-link {
  color: var(--accent);
}

/* Day-mode command output uses the same warm paper hierarchy as the rest of
   the Japanese-minimal palette instead of leaving a heavy dark rectangle in
   the transcript. Syntax colours are muted but remain contrast-safe. */
:root[data-theme='light'] .msg-text pre,
:root[data-theme='light'] .event-body pre {
  background: var(--bg-input);
  border-color: var(--border);
}

:root[data-theme='light'] .msg-text pre code,
:root[data-theme='light'] .event-body pre code {
  color: var(--text);
}

:root[data-theme='light'] .hljs-comment,
:root[data-theme='light'] .hljs-quote {
  color: #6f6a61;
}

:root[data-theme='light'] .hljs-keyword,
:root[data-theme='light'] .hljs-selector-tag,
:root[data-theme='light'] .hljs-literal,
:root[data-theme='light'] .hljs-type,
:root[data-theme='light'] .hljs-doctag {
  color: #963f43;
}

:root[data-theme='light'] .hljs-title,
:root[data-theme='light'] .hljs-title.class_,
:root[data-theme='light'] .hljs-title.function_,
:root[data-theme='light'] .hljs-section {
  color: #6e5c87;
}

:root[data-theme='light'] .hljs-string,
:root[data-theme='light'] .hljs-regexp,
:root[data-theme='light'] .hljs-template-tag,
:root[data-theme='light'] .hljs-template-variable {
  color: #315f73;
}

:root[data-theme='light'] .hljs-number,
:root[data-theme='light'] .hljs-symbol,
:root[data-theme='light'] .hljs-bullet,
:root[data-theme='light'] .hljs-link {
  color: #765322;
}

:root[data-theme='light'] .hljs-attr,
:root[data-theme='light'] .hljs-attribute,
:root[data-theme='light'] .hljs-variable,
:root[data-theme='light'] .hljs-property,
:root[data-theme='light'] .hljs-params {
  color: #8c4f2b;
}

:root[data-theme='light'] .hljs-built_in,
:root[data-theme='light'] .hljs-builtin-name,
:root[data-theme='light'] .hljs-meta {
  color: #6f5d24;
}

:root[data-theme='light'] .hljs-tag,
:root[data-theme='light'] .hljs-name,
:root[data-theme='light'] .hljs-selector-id,
:root[data-theme='light'] .hljs-selector-class {
  color: #397057;
}

:root[data-theme='light'] .hljs-addition {
  color: #2f684f;
  background: #e6f0e9;
}

:root[data-theme='light'] .hljs-deletion {
  color: #963f43;
  background: #f7e8e5;
}

/* Mermaid is rendered with an explicit dark diagram palette, so its chrome
   remains dark as one self-contained figure in either app appearance. */
:root[data-theme='light'] .mermaid-wrap,
:root[data-theme='light'] .mm-modal-content {
  background: #1c1c1a;
  border-color: #51534d;
  color: #e6e0d5;
}

:root[data-theme='light'] .mermaid-header,
:root[data-theme='light'] .mm-modal-head {
  background: #252622;
  border-color: #51534d;
  color: #b9b5ab;
}

:root[data-theme='light'] .mermaid-title,
:root[data-theme='light'] .mm-modal-title {
  color: #f4f0e6;
}

/* A table is not a dark figure: its viewer follows the app appearance. */
:root[data-theme='light'] .mermaid-modal[data-kind='table'] .mm-modal-content {
  background: var(--bg-alt);
  border-color: var(--border);
  color: var(--text);
}

:root[data-theme='light'] .mermaid-modal[data-kind='table'] .mm-modal-head {
  background: var(--bg-alt);
  border-color: var(--border);
}

:root[data-theme='light'] .mermaid-modal[data-kind='table'] .mm-modal-title {
  color: var(--text-bright);
}

:root[data-theme='light'] .mermaid-btn {
  background: #31322e;
  border-color: #5d6058;
  color: #e6e0d5;
}

:root[data-theme='light'] .deleted-banner {
  background: #fcf6e8;
  color: #655630;
}

:root[data-theme='light'] .login-error,
:root[data-theme='light'] .trash-actions .danger,
:root[data-theme='light'] .trash-bulk-bar .trash-bulk-danger,
:root[data-theme='light'] .icon-btn.danger,
:root[data-theme='light'] .menu-item.danger {
  color: #b4232a;
}

:root[data-theme='light'] .trash-actions .danger,
:root[data-theme='light'] .trash-bulk-bar .trash-bulk-danger {
  border-color: #e3a9ac;
}

:root[data-theme='light'] .menu-item.danger:hover,
:root[data-theme='light'] .menu-item.danger:focus-visible {
  background: rgb(201 52 58 / 0.1);
}

:root[data-theme='light'] .provider-badge {
  background: #ffffff;
  border-color: var(--border);
}

:root[data-theme='light'] .provider-badge.local,
:root[data-theme='light'] .notif-state.on {
  color: #356348;
}

:root[data-theme='light'] .notif-state.on {
  background: #f0f5f1;
}

:root[data-theme='light'] .provider-badge.nv {
  color: #4d7400;
}

:root[data-theme='light'] .provider-badge.gpt {
  color: #087761;
}

:root[data-theme='light'] .provider-badge.astra {
  color: #9d174d;
}

:root[data-theme='light'] .provider-badge.terra {
  color: #963f2b;
}

:root[data-theme='light'] .provider-badge.sol {
  color: #805c00;
}

:root[data-theme='light'] .provider-badge.luna {
  color: #52606e;
}

:root[data-theme='light'] .provider-badge.gem {
  color: #245cb5;
}

:root[data-theme='light'] .provider-badge.kimi {
  color: #6741ad;
}

:root[data-theme='light'] .provider-badge.claude,
:root[data-theme='light'] .provider-badge.opus,
:root[data-theme='light'] .provider-badge.sonnet,
:root[data-theme='light'] .provider-badge.haiku,
:root[data-theme='light'] .provider-badge.fable {
  background: #fff8f5;
  border-color: #dba38f;
  color: #93452e;
}

:root[data-theme='light'] .provider-badge.xai {
  background: #ffffff;
  border-color: var(--border);
  color: var(--text);
}

:root[data-theme='light'] .provider-badge.or {
  color: #6741ad;
}

:root[data-theme='light'] .notif-state.blocked {
  color: #a52b31;
}

:root[data-theme='light'] .toast {
  background: #ffffff;
  border-color: var(--border);
  box-shadow: 0 12px 32px rgb(45 43 38 / 0.12);
  color: var(--text-bright);
}

:root[data-theme='light'] ::selection {
  background: rgb(63 95 111 / 0.22);
}

:root[data-theme='light'] .sel-bubble {
  filter: drop-shadow(0 6px 14px rgb(63 95 111 / 0.2));
}

:root[data-theme='light'] .sel-bubble-fill-top { stop-color: rgb(150 196 214 / 0.3); }
:root[data-theme='light'] .sel-bubble-fill-bottom { stop-color: rgb(63 95 111 / 0.2); }
:root[data-theme='light'] .sel-bubble-rim-top { stop-color: rgb(255 255 255 / 0.95); }
:root[data-theme='light'] .sel-bubble-rim-bottom { stop-color: rgb(63 95 111 / 0.4); }

:root[data-theme='light'] .selection-actions {
  border-color: rgb(255 255 255 / 0.8);
  background:
    radial-gradient(120% 90% at 30% 0%, rgb(255 255 255 / 0.75), transparent 60%),
    rgb(240 244 246 / 0.55);
  box-shadow:
    inset 0 1px 1px rgb(255 255 255 / 0.9),
    inset 0 -6px 14px rgb(63 95 111 / 0.12),
    0 12px 30px rgb(45 43 38 / 0.18);
}

:root[data-theme='light'] .selection-actions button {
  color: var(--text-bright);
}

:root[data-theme='light'] .selection-actions button:active {
  background: rgb(63 95 111 / 0.12);
}

:root[data-theme='light'] .selection-actions button:disabled {
  color: rgb(63 95 111 / 0.4);
}

/* The lightbox is always dark, including labels that would otherwise inherit
   the daytime text tokens. */
:root[data-theme='light'] .lb-thumb,
:root[data-theme='light'] .lb-thumb-num {
  color: #b5bac1;
}

/* LobeHub owns reply animation. These rules only fit existing native layout
 * and interactions; the package injects its own fade CSS without overrides. */
.msg-text.lobe-rich,
.event-body.lobe-rich {
  /* HAST's inter-block newlines are separators, not visible blank lines.
     Prose line breaks already have <br>; leave raw user/tool text untouched. */
  white-space: normal;
}
.lobe-rich :not(pre) > code {
  white-space: pre-wrap;
}
.msg-text > .streamdown-animated > :first-child {
  margin-top: 0;
}
.msg-text > .streamdown-animated > :last-child {
  margin-bottom: 0;
}
.streamdown-animated .stream-char[data-lobe-fading] {
  user-select: none;
}
.streamdown-animated .msg-inline-media {
  display: block;
}

/* A soft cursor so a pause in generation still looks alive rather than stuck. */
.msg.partial .msg-text::after {
  content: '';
  display: inline-block;
  width: 2px;
  height: 1em;
  margin-left: 2px;
  vertical-align: text-bottom;
  background: currentColor;
  opacity: 0.5;
  animation: partial-caret 1s steps(2, start) infinite;
}

@keyframes partial-caret {
  50% {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .msg.partial .msg-text::after {
    animation: none;
  }
}

/* Thinking uses the same ready-block queue and keeps its expanded state. */
.event.thinking.partial .event-body::after {
  content: '';
  display: inline-block;
  width: 2px;
  height: 1em;
  margin-left: 2px;
  vertical-align: text-bottom;
  background: currentColor;
  opacity: 0.4;
  animation: partial-caret 1s steps(2, start) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .event.thinking.partial .event-body::after {
    animation: none;
  }
}

/* Subagents: quiet native sheet, stable cards, shared conversation renderer. */
.subagents-dialog {
  box-sizing: border-box;
  width: min(calc(100% - 32px), 720px);
  height: min(calc(100dvh - 48px), 860px);
  max-width: 100%;
  max-height: 100dvh;
  margin: auto;
  padding: 0;
  border: 1px solid var(--border, #42444d);
  border-radius: 16px;
  background: var(--bg);
  color: var(--text);
  box-shadow: 0 24px 80px #0005;
  font-family: inherit;
  overflow: hidden;
}
.subagents-dialog[open] { display: flex; flex-direction: column; }
.subagents-surface { position: relative; z-index: 1; display: flex; flex-direction: column; flex: 1; min-height: 0; width: 100%; background: var(--bg); }
.subagents-swipe-preview { display: none; position: absolute; inset: 0; flex-direction: column; background: var(--bg); pointer-events: none; }
.subagents-dialog[data-dragging='detail'] .subagents-swipe-preview { display: flex; }
.subagents-dialog[data-dragging] { background: transparent; box-shadow: none; }
.subagents-dialog[data-dragging] .subagents-surface { will-change: transform; }
.subagents-dialog[data-dragging='list']::backdrop { background: transparent; backdrop-filter: none; }
.subagents-dialog::backdrop { background: #0008; backdrop-filter: blur(4px); }
.subagents-header { display: flex; align-items: center; gap: 8px; padding: max(16px, env(safe-area-inset-top)) 16px 4px; flex-shrink: 0; }
.subagents-heading { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; }
.subagents-header h2 { min-width: 0; margin: 0; font-size: 20px; font-weight: 650; letter-spacing: -.4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-bright); }
.subagents-count { border-radius: 8px; padding: 3px 7px; background: var(--bg-input); color: var(--text-2); font-size: 12px; font-variant-numeric: tabular-nums; }
.subagents-dialog button { min-width: 44px; min-height: 44px; font: inherit; color: inherit; background: transparent; border: 1px solid transparent; border-radius: 12px; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background 160ms ease, border-color 160ms ease; }
.subagents-header button { flex-shrink: 0; font-size: 22px; color: var(--text-2); }
.subagents-dialog button:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }
.subagents-dialog button:active { background: var(--bg-hover); }
.subagents-note { margin: 0; padding: 0 20px 16px; border-bottom: 1px solid var(--border); font-size: 12px; line-height: 1.5; color: var(--text-2); overflow-wrap: anywhere; }
.subagents-body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; padding: 20px 16px max(24px, env(safe-area-inset-bottom)); scrollbar-gutter: stable; }
.subagents-list { display: grid; gap: 12px; }
.subagents-dialog .subagent-row { display: flex; flex-direction: column; width: 100%; padding: 16px; text-align: left; gap: 12px; border-color: var(--border); background: var(--bg-alt); }
.subagent-top { display: flex; align-items: center; width: 100%; gap: 10px; }
.subagent-number { display: grid; place-items: center; width: 30px; height: 30px; flex-shrink: 0; border-radius: 9px; background: var(--bg-input); color: var(--text-2); font-size: 11px; font-variant-numeric: tabular-nums; }
.subagent-name { font-size: 15px; font-weight: 600; color: var(--text-bright); min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.subagent-chevron { margin-left: auto; color: var(--text-dim); font-size: 22px; }
.subagent-task { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; overflow-wrap: anywhere; line-height: 1.6; color: var(--text-2); font-size: 13px; }
.subagent-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; width: 100%; font-size: 11px; }
.subagent-model { color: var(--text-2); overflow-wrap: anywhere; min-width: 0; }
.subagent-state { display: inline-flex; align-items: center; gap: 5px; color: var(--text-2); margin-left: auto; }
.subagent-state::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: var(--text-dim); flex-shrink: 0; }
.subagent-state[data-tone='ready']::before { background: var(--tool); }
.subagent-state[data-tone='running'] { color: var(--text-bright); }
.subagent-state[data-tone='running']::before { width: 12px; height: 12px; background: transparent; border: 2px solid var(--border); border-top-color: var(--accent-light); animation: subagent-spin .8s linear infinite; }
@keyframes subagent-spin { to { transform: rotate(360deg); } }
.subagent-state[data-tone='attention']::before { background: var(--danger); }
.subagents-empty { text-align: center; padding: clamp(40px, 12vh, 120px) 20px; color: var(--text-2); }
.subagents-empty-icon { display: grid; place-items: center; margin: 0 auto 20px; width: 56px; height: 56px; border: 1px solid var(--border); border-radius: 18px; font-size: 32px; color: var(--accent-light); background: var(--bg-alt); }
.subagents-empty h3 { font-size: 17px; color: var(--text-bright); font-weight: 600; margin: 0 0 10px; }
.subagents-empty p { max-width: 280px; margin: auto; font-size: 13px; line-height: 1.7; }
.subagents-older { flex-shrink: 0; margin: 8px 16px 0; color: var(--text-2); }
.subagents-dialog[data-view='detail'] .subagents-body { padding-top: 20px; }
@media (hover: hover) { .subagents-dialog button:hover { background: var(--bg-hover); } .subagents-dialog .subagent-row:hover { border-color: var(--text-dim); } }
@media (max-width: 767px) { .subagents-dialog { width: 100%; border-radius: 0; height: 100dvh; border: 0; } }
@media (prefers-reduced-motion: reduce) { .subagents-dialog button { transition: none; } .subagent-state[data-tone='running']::before { animation: none; } }

/* ── Videos in the chat and the media album ── */

.video-file {
  position: relative;
}

/* The chat card is a poster: tapping it plays it in the stream player dock. */
.video-open {
  position: absolute;
  inset: 0 0 45px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  touch-action: manipulation;
}

.video-open::before {
  content: '';
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: rgb(0 0 0 / 0.55) no-repeat 55% 50% / 22px
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M7 4.5v15l13-7.5z'/%3E%3C/svg%3E");
  box-shadow: 0 4px 16px rgb(0 0 0 / 0.35);
  backdrop-filter: blur(4px);
}

#lb-video {
  width: 100%;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 14px;
  background: #000;
  box-shadow: 0 18px 50px rgb(0 0 0 / 0.6);
  will-change: transform, opacity;
}

.lb-thumb video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
}

.lb-thumb video.loaded {
  opacity: 1;
}

.lb-thumb-play {
  position: absolute;
  z-index: 1;
  right: 3px;
  bottom: 2px;
  font-size: 10px;
  color: #fff;
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.8);
  pointer-events: none;
}

/* New Life is stopping the running Life turn before archiving it. */
#btn-life-new-session[aria-busy='true'] svg {
  animation: media-download-pulse 0.8s ease-in-out infinite alternate;
}
