/* Catppuccin theme for the rustorrent web UI. The palette custom properties
   below are the exact Catppuccin Mocha hex values the TUI ships
   (`crates/rustorrent-tui/src/theme.rs` :: MOCHA), so the terminal and the web
   frontend read as one product. Auto mode (no `data-theme` attribute) pairs
   Mocha (dark, default) with Catppuccin Latte (under `prefers-color-scheme: light`).
   Explicit themes are defined as `:root[data-theme="<id>"]` CSS blocks below. */

/* Bundled JetBrains Mono (skin-A). woff2 shipped from `assets/` (see the
   index.html copy-dir link) — self-contained, no Google Fonts CDN. Only
   400/700 are bundled; 500/600 requests fall back to the nearest weight. */
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/JetBrainsMono-Regular.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("assets/JetBrainsMono-Bold.woff2") format("woff2");
}

:root {
  color-scheme: light dark;

  /* Catppuccin Mocha (dark, default). */
  --base: #1e1e2e;
  --mantle: #181825;
  --crust: #11111b;
  --surface0: #313244;
  --surface1: #45475a;
  --surface2: #585b70;
  --overlay0: #6c7086;
  --overlay1: #7f849c;
  --overlay2: #9399b2;
  --subtext0: #a6adc8;
  --subtext1: #bac2de;
  --text: #cdd6f4;
  --blue: #89b4fa;
  --sapphire: #74c7ec;
  --sky: #89dceb;
  --teal: #94e2d5;
  --green: #a6e3a1;
  --yellow: #f9e2af;
  --peach: #fab387;
  --red: #f38ba8;
  --maroon: #eba0ac;
  --mauve: #cba6f7;
  --pink: #f5c2e7;
  --lavender: #b4befe;

  --font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --accent: var(--blue);
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Catppuccin Latte (light) — only when the user prefers a light scheme. */
@media (prefers-color-scheme: light) {
  :root {
    --base: #eff1f5;
    --mantle: #e6e9ef;
    --crust: #dce0e8;
    --surface0: #ccd0da;
    --surface1: #bcc0cc;
    --surface2: #acb0be;
    --overlay0: #9ca0b0;
    --overlay1: #8c8fa1;
    --overlay2: #7c7f93;
    --subtext0: #6c6f85;
    --subtext1: #5c5f77;
    --text: #4c4f69;
    --blue: #1e66f5;
    --sapphire: #209fb5;
    --sky: #04a5e5;
    --teal: #179299;
    --green: #40a02b;
    --yellow: #df8e1d;
    --peach: #fe640b;
    --red: #d20f39;
    --maroon: #e64553;
    --mauve: #8839ef;
    --pink: #ea76cb;
    --lavender: #7287fd;
  }
}

/* ── Themes (Web-Appearance) ─────────────────────────────────────────────────
   Explicit theme = `data-theme` on <html> (stamped by the app + the pre-WASM
   script in index.html). `:root[data-theme]` (0,2,0) beats both the `:root`
   default and the media query above (0,1,0), so an explicit choice wins over
   the system scheme in both directions. No attribute = Auto (Mocha/Latte).
   The accent rules sit BEFORE the theme blocks on purpose: client-inspired
   themes fix --accent inside their block and must win over a stale
   data-accent attribute. Every block carries all 24 tokens + color-scheme —
   enforced by model.rs::app_css_defines_every_theme_block_with_all_tokens. */
:root[data-accent="blue"] { --accent: var(--blue); }
:root[data-accent="mauve"] { --accent: var(--mauve); }
:root[data-accent="green"] { --accent: var(--green); }
:root[data-accent="peach"] { --accent: var(--peach); }

/* Catppuccin Mocha — explicit pick (same values as the :root default). */
:root[data-theme="mocha"] {
  color-scheme: dark;
  --base: #1e1e2e; --mantle: #181825; --crust: #11111b;
  --surface0: #313244; --surface1: #45475a; --surface2: #585b70;
  --overlay0: #6c7086; --overlay1: #7f849c; --overlay2: #9399b2;
  --subtext0: #a6adc8; --subtext1: #bac2de; --text: #cdd6f4;
  --blue: #89b4fa; --sapphire: #74c7ec; --sky: #89dceb; --teal: #94e2d5;
  --green: #a6e3a1; --yellow: #f9e2af; --peach: #fab387; --red: #f38ba8;
  --maroon: #eba0ac; --mauve: #cba6f7; --pink: #f5c2e7; --lavender: #b4befe;
}

/* Catppuccin Macchiato — hexes from rustorrent-tui theme.rs::MACCHIATO. */
:root[data-theme="macchiato"] {
  color-scheme: dark;
  --base: #24273a; --mantle: #1e2030; --crust: #181926;
  --surface0: #363a4f; --surface1: #494d64; --surface2: #5b6078;
  --overlay0: #6e738d; --overlay1: #8087a2; --overlay2: #939ab7;
  --subtext0: #a5adcb; --subtext1: #b8c0e0; --text: #cad3f5;
  --blue: #8aadf4; --sapphire: #7dc4e4; --sky: #91d7e3; --teal: #8bd5ca;
  --green: #a6da95; --yellow: #eed49f; --peach: #f5a97f; --red: #ed8796;
  --maroon: #ee99a0; --mauve: #c6a0f6; --pink: #f5bde6; --lavender: #b7bdf8;
}

/* Catppuccin Latte — explicit pick (same values as the light media query). */
:root[data-theme="latte"] {
  color-scheme: light;
  --base: #eff1f5; --mantle: #e6e9ef; --crust: #dce0e8;
  --surface0: #ccd0da; --surface1: #bcc0cc; --surface2: #acb0be;
  --overlay0: #9ca0b0; --overlay1: #8c8fa1; --overlay2: #7c7f93;
  --subtext0: #6c6f85; --subtext1: #5c5f77; --text: #4c4f69;
  --blue: #1e66f5; --sapphire: #209fb5; --sky: #04a5e5; --teal: #179299;
  --green: #40a02b; --yellow: #df8e1d; --peach: #fe640b; --red: #d20f39;
  --maroon: #e64553; --mauve: #8839ef; --pink: #ea76cb; --lavender: #7287fd;
}

/* Tokyo Night — hexes from rustorrent-tui theme.rs::TOKYO_NIGHT. */
:root[data-theme="tokyo-night"] {
  color-scheme: dark;
  --base: #1a1b26; --mantle: #16161e; --crust: #13131a;
  --surface0: #292e42; --surface1: #343a52; --surface2: #3b4261;
  --overlay0: #545c7e; --overlay1: #565f89; --overlay2: #737aa2;
  --subtext0: #a9b1d6; --subtext1: #b4c2f0; --text: #c0caf5;
  --blue: #7aa2f7; --sapphire: #2ac3de; --sky: #7dcfff; --teal: #1abc9c;
  --green: #9ece6a; --yellow: #e0af68; --peach: #ff9e64; --red: #f7768e;
  --maroon: #db4b4b; --mauve: #bb9af7; --pink: #ff75a0; --lavender: #9d7cd8;
}

/* Oxide — rustorrent original: warm graphite neutrals, rust-orange family. */
:root[data-theme="oxide"] {
  color-scheme: dark;
  --base: #1b1c1f; --mantle: #161719; --crust: #101113;
  --surface0: #2a2c30; --surface1: #3a3d42; --surface2: #4a4e54;
  --overlay0: #5f646b; --overlay1: #757b83; --overlay2: #8b929b;
  --subtext0: #a4aab2; --subtext1: #bcc2c9; --text: #d6dade;
  --blue: #6da8d8; --sapphire: #5aa7c7; --sky: #7cc3e8; --teal: #58b8a6;
  --green: #8fbf71; --yellow: #d9a94c; --peach: #e06c3a; --red: #d95f57;
  --maroon: #c05046; --mauve: #a78bc9; --pink: #cf7fa6; --lavender: #97a3d0;
}

/* Oxide Light — the light pair: warm near-white base, graphite text. */
:root[data-theme="oxide-light"] {
  color-scheme: light;
  --base: #f3f2ef; --mantle: #eae8e4; --crust: #dfdcd7;
  --surface0: #d3d0ca; --surface1: #c2beb7; --surface2: #aca8a0;
  --overlay0: #8f8c86; --overlay1: #75736e; --overlay2: #5f5d59;
  --subtext0: #504e4a; --subtext1: #403f3c; --text: #33322f;
  --blue: #3d6f99; --sapphire: #2f7f96; --sky: #2f95c4; --teal: #2f8f7f;
  --green: #4f8a35; --yellow: #8f6a1f; --peach: #c04f1d; --red: #b03a32;
  --maroon: #963028; --mauve: #7a5ba3; --pink: #a84f7d; --lavender: #5b6ea8;
}

/* Qube Light — qBittorrent-inspired: light Qt grays, desktop blue. */
:root[data-theme="qube-light"] {
  color-scheme: light;
  --accent: #2f6db3;
  --base: #f0f0f0; --mantle: #e8e8e8; --crust: #dcdcdc;
  --surface0: #d0d0d0; --surface1: #c0c0c0; --surface2: #a8a8a8;
  --overlay0: #8e8e8e; --overlay1: #747474; --overlay2: #5f5f5f;
  --subtext0: #4c4c4c; --subtext1: #3a3a3a; --text: #202020;
  --blue: #2f6db3; --sapphire: #2a7f9e; --sky: #3391cc; --teal: #2e8b80;
  --green: #2e9e3f; --yellow: #b8860b; --peach: #d2691e; --red: #c62828;
  --maroon: #a03030; --mauve: #7b4fa6; --pink: #b0508a; --lavender: #5c6bc0;
}

/* Qube Dark — qBittorrent-inspired dark mode: neutral dark grays, same blue. */
:root[data-theme="qube-dark"] {
  color-scheme: dark;
  --accent: #5c9fd8;
  --base: #2b2b2b; --mantle: #242424; --crust: #1c1c1c;
  --surface0: #3a3a3a; --surface1: #474747; --surface2: #555555;
  --overlay0: #6a6a6a; --overlay1: #7f7f7f; --overlay2: #949494;
  --subtext0: #aaaaaa; --subtext1: #c0c0c0; --text: #dcdcdc;
  --blue: #5c9fd8; --sapphire: #56b2c4; --sky: #6fc3e8; --teal: #55b8a5;
  --green: #66bb6a; --yellow: #d4b04a; --peach: #e08a4e; --red: #e57373;
  --maroon: #c85a5a; --mauve: #ab7fd4; --pink: #d47fae; --lavender: #8f9fdc;
}

/* Cupertino — Transmission-inspired: neutral macOS grays, system blue. */
:root[data-theme="cupertino"] {
  color-scheme: light;
  --accent: #0a68da;
  --base: #f5f5f7; --mantle: #ececee; --crust: #e0e0e3;
  --surface0: #d8d8dc; --surface1: #c8c8cd; --surface2: #b0b0b6;
  --overlay0: #96969c; --overlay1: #7c7c82; --overlay2: #66666c;
  --subtext0: #545458; --subtext1: #424246; --text: #1d1d1f;
  --blue: #0a68da; --sapphire: #0e94ad; --sky: #2aa0e0; --teal: #2f9d8f;
  --green: #3f9d3f; --yellow: #c98a00; --peach: #e8722a; --red: #c8362f;
  --maroon: #a52a2a; --mauve: #7d55c7; --pink: #c14f8e; --lavender: #5e5ce6;
}

/* Retro Lime — uTorrent-classic-inspired: near-white base, lime signature.
   --accent is a darkened lime: the raw #7aba00 signature stays on --green
   (bars, brand chip), but as SMALL TEXT on the near-white base it reads at
   ~2.3:1 — the darker shade keeps the hue at 4.8:1. */
:root[data-theme="retro-lime"] {
  color-scheme: light;
  --accent: #4f7d00;
  --base: #fbfbf8; --mantle: #f1f1ec; --crust: #e4e4de;
  --surface0: #d9d9d2; --surface1: #c8c8c0; --surface2: #b0b0a8;
  --overlay0: #94948c; --overlay1: #7a7a73; --overlay2: #63635d;
  --subtext0: #52524c; --subtext1: #41413c; --text: #262622;
  --blue: #3a7abd; --sapphire: #3390a8; --sky: #3ba4d4; --teal: #2f9184;
  --green: #7aba00; --yellow: #9a7d0a; --peach: #cc6f1f; --red: #bb3d32;
  --maroon: #99332a; --mauve: #7e58a8; --pink: #ad4f85; --lavender: #5c74b8;
}

/* Steel — Deluge/ruTorrent-inspired: cool blue-steel light. */
:root[data-theme="steel"] {
  color-scheme: light;
  --accent: #41698c;
  --base: #eef1f4; --mantle: #e3e8ee; --crust: #d5dce4;
  --surface0: #c9d2db; --surface1: #b6c2ce; --surface2: #9dabb9;
  --overlay0: #8493a2; --overlay1: #6b7a89; --overlay2: #576573;
  --subtext0: #48545f; --subtext1: #39434d; --text: #24303a;
  --blue: #41698c; --sapphire: #34808f; --sky: #3c8fc0; --teal: #2f8a7c;
  --green: #3f8f3f; --yellow: #9c7c1a; --peach: #c76a24; --red: #b53d35;
  --maroon: #94322b; --mauve: #74569e; --pink: #a44e7f; --lavender: #5a6f9e;
}

/* Dim note line under the Appearance selectors. */
.form-note {
  color: var(--subtext0);
  font-size: 12px;
  margin: 4px 0 0;
}

* {
  box-sizing: border-box;
}

/* Firefox ONLY (Chromium never matches -moz-appearance): thin classic
   scrollbar in the skin's colors. Must NOT reach Chromium — when the standard
   scrollbar-width/scrollbar-color properties are set, Chromium (121+) ignores
   the ::-webkit-scrollbar styling below and falls back to its auto-hiding
   scrollbar. */
@supports (-moz-appearance: none) {
  * {
    scrollbar-width: thin;
    scrollbar-color: var(--surface2) var(--surface0);
  }
}

/* Classic (non-overlay) scrollbars everywhere: styling the WebKit scrollbar
   turns off Chromium's overlay mode, so a scrollable pane's bar occupies its
   own layout column instead of floating OVER the content (it was covering the
   Settings inputs), and both the track and the thumb stay PERMANENTLY visible
   whenever the pane overflows — an immediate cue that something is out of
   view. */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
::-webkit-scrollbar-track {
  background: var(--surface0);
  border-radius: 4px;
}
::-webkit-scrollbar-thumb {
  background: var(--surface2);
  border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--overlay0);
}
::-webkit-scrollbar-corner {
  background: transparent;
}

html,
body {
  height: 100%;
}

body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--base);
  color: var(--text);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.4;
}

.app-main {
  min-width: 0;
}

/* ── Toolbar: add-magnet field ──────────────────────────────────────────── */

.add-error {
  color: var(--red);
  font-size: 0.82rem;
}

.file-add-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Login screen (from Task 7; themed here) ────────────────────────────── */

.login-screen {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 70vh;
}

.login-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  width: min(20rem, 90vw);
  padding: 1.5rem;
  background: var(--mantle);
  border: 1px solid var(--surface0);
  border-radius: 0.75rem;
}

.login-form h1 {
  margin: 0 0 0.5rem;
  font-size: 1.25rem;
  color: var(--mauve);
  text-align: center;
}

.login-form label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.85rem;
  color: var(--subtext0);
}

.login-form input {
  padding: 0.5rem 0.6rem;
  background: var(--base);
  color: var(--text);
  border: 1px solid var(--surface1);
  border-radius: 0.4rem;
  font: inherit;
}

.login-form input:focus {
  outline: none;
  border-color: var(--blue);
}

.login-form button {
  margin-top: 0.5rem;
  padding: 0.55rem;
  background: var(--blue);
  color: var(--crust);
  border: none;
  border-radius: 0.4rem;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.login-form button:hover {
  background: var(--sapphire);
}

.login-error {
  margin: 0;
  color: var(--red);
  font-size: 0.85rem;
  text-align: center;
}

/* ── Workspace layout ────────────────────────────────────────────────────
   Desktop: the skin-A shell (`.web.skin-terminal`, defined in the layout
   block near the end of this file) stacks toolbar / `.wbody` / statusbar,
   where `.wbody` is a filters rail beside a column that stacks the transfer
   list over the detail pane (`.wlist-region` / `.wdivider` / `.wdet-region`).
   Mobile (`max-width: 640px`): selecting a torrent takes over the screen —
   `.toolbar`/`.list-pane` hide, `.detail-col` fills the width, and the
   "← Back" button (hidden on desktop) clears `selected` to return to the
   list. Nothing selected → the list shows and the detail column hides. ── */

.list-pane,
.detail-col {
  min-width: 0;
}

.back-btn {
  display: none;
}

@media (max-width: 640px) {
  .back-btn {
    display: inline-flex;
    margin-bottom: 0.5rem;
  }

  .app-main.has-selection .wtoolbar,
  .app-main.has-selection .list-pane {
    display: none;
  }

  .app-main:not(.has-selection) .detail-col {
    display: none;
  }
}

/* ── Detail pane (Task 4 scaffold: tab strip + body) ────────────────────── */

.detail-pane {
  margin-top: 0.75rem;
  border: 1px solid var(--surface0);
  border-radius: 0.5rem;
  overflow: hidden;
}

.detail-body {
  /* No vertical padding: it would show as a gap above the sticky `.dtbl-head`
     at scroll-top and below the sticky `.wdet-foot` at scroll-bottom (and both
     at once on a non-scrolling tab). Horizontal padding stays. */
  padding: 0 1rem;
  min-height: 4rem;
}

.detail-empty {
  color: var(--overlay1);
  text-align: center;
  padding: 1.5rem 0;
}

/* ── General tab (Task 5): two-column key/value grid + time line + bar ──── */

.general-tab {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.general-grid {
  display: grid;
  /* minmax(0,1fr), not 1fr: a `1fr` track floors at min-content, so a long
     unbreakable value (e.g. the Save-path) blows the left column out and shoves
     the right column off-screen. minmax(0,…) lets the `.kv .v` ellipsize. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0 26px;
}

.general-timeline {
  margin: 0;
  font-size: 0.85rem;
  color: var(--subtext0);
}

.general-error {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--red);
}

.general-bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.general-bar-track {
  flex: 1 1 auto;
  display: flex;
  height: 0.6rem;
  background: var(--surface0);
  border-radius: 0;
  overflow: hidden;
}

.general-bar-seg {
  flex: 0 0 auto;
  height: 100%;
  border-radius: 0;
}

.general-bar-green {
  background: var(--green);
}

.general-bar-grey {
  background: var(--surface2);
}

.general-bar-blue {
  background: var(--blue);
}

.general-bar-label {
  flex: 0 0 auto;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--subtext1);
}

/* Stack the two-column grid under a narrow viewport. */
@media (max-width: 640px) {
  .general-grid {
    grid-template-columns: 1fr;
  }
}

/* ── Trackers tab (skin-A): .dtbl grid + DHT/PeX/LSD pseudo-rows ─────────── */

.tr-status {
  font-weight: 600;
}

.tr-ok {
  color: var(--green);
}

.tr-pending {
  color: var(--yellow);
}

.tr-error {
  color: var(--red);
}

.tr-off {
  color: var(--overlay1);
}

/* ── Pieces tab (Task 9): canvas piece-map grid + stats line + legend ───── */

/* Fills the pane exactly (same chain as the Speed tab): the shrinkable wrap
   bounds the flex-grown .pmap-host, whose MEASURED size fixes the grid — the
   map never scrolls and never depends on the piece count. */
.pieces-tab-wrap {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  /* A little breathing room above the map, matching the inter-row gap —
     `.detail-body` dropped its vertical padding to keep sticky headers flush,
     which left this (header-less) tab's map against the top. */
  padding-top: 0.6rem;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.pmap-host {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.pieces-canvas {
  display: block;
  image-rendering: pixelated;
}

.pieces-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 22px;
  margin: 6px 0 0;
  font-size: 11px;
  color: var(--subtext0);
}

.pieces-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-top: 8px;
}

.legend-item {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.pc {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 1px;
  vertical-align: middle;
}

.speed-tab-wrap {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
/* The Speed tab fills the (bounded, resizable) detail pane EXACTLY instead of
   the generic grow-to-content shell (#2). The generic `.wdet-tab` uses
   `flex: 1 0 auto; min-height: 100%` (shrink 0) so scroll tabs push their footer
   down — but that gives no definite bounded height, so the flex-1 chart's
   `height: 100%` couldn't resolve and the SVG overgrew the pane, needing a huge
   window. `.wdet-fill` (the Speed and Pieces tabs) makes the shell shrink to
   the pane's real height, and the shrinkable `.speed-tab-wrap` /
   `.pieces-tab-wrap` + `overflow: hidden` keep the chart/map within it. */
.wdet-tab.wdet-fill {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.wtab-scroll.speed-tab-wrap {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
/* Same fix for the Pieces tab: the generic `.wtab-scroll { flex: 1 0 auto }`
   (shrink 0) appears later in the sheet and would otherwise win over the
   single-class `.pieces-tab-wrap`, latching the wrap so it grows but never
   shrinks (the measured `.pmap-host` then keeps its stale tall height, so the
   map rescaled on grow but not on shrink until the tab was remounted). This
   two-class rule restores `flex-shrink: 1` so the map tracks the pane both ways. */
.wtab-scroll.pieces-tab-wrap {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/* ── Speed tab (R5): one combined SVG (download area+line, upload line) ──── */

.speed-head {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 6px;
  font-size: 12px;
}

.speed-head-meta { margin-left: auto; }

.spdwrap {
  display: flex;
  gap: 8px;
  align-items: stretch;
  /* Grow to fill the leftover height between the head legend and the footer so
     the chart stretches to the full tab height (item 3). preserveAspectRatio
     ="none" on the SVG stretches the drawing to match. */
  flex: 1;
  min-height: 0;
}

.spdaxis {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--overlay0);
  text-align: right;
  width: 48px;
  padding: 2px 0;
}

.spdsvg-host {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
}

.spdsvg {
  width: 100%;
  height: 100%;
  border-left: 1px solid var(--surface1);
  border-bottom: 1px solid var(--surface1);
}

.spdsvg .grid { stroke: var(--surface0); stroke-width: 1; }

.speed-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 22px;
  margin-top: 8px;
  padding-top: 6px;
  border-top: 1px solid var(--surface1);
}

/* ── Filters side panel ────────────────────────────────────────────────── */

.filter-section { margin-bottom: 0.75rem; }

/* Log content styling (still used by LogWindow inside its .wmbody). */
.log-lines { list-style: none; margin: 0; padding: 0; font-size: 0.85rem; }
.log-line { display: flex; gap: 0.5rem; padding: 0.05rem 0; }
.log-ts { color: var(--overlay0, #6c7086); }
.log-info .log-marker { color: var(--blue, #89b4fa); }
.log-warn .log-marker { color: var(--yellow, #f9e2af); }
.log-error .log-marker { color: var(--red, #f38ba8); }
.log-text { color: var(--subtext1, #bac2de); }
.log-empty { color: var(--overlay0, #6c7086); text-align: center; padding: 1rem; }
.log-count { margin-left: auto; color: var(--overlay0, #6c7086); }

/* Settings-screen controls: skin-A look on the kept .form-row/.btn classes. */
.form-row { display: flex; align-items: center; gap: .5rem; justify-content: space-between; }
.form-row .form-label { color: var(--overlay0); flex: 1; }
.form-row input[type="text"], .form-row input[type="number"], .form-row input[type="password"], .form-row select {
  background: var(--mantle); border: 1px solid var(--surface1); border-radius: 0;
  padding: 3px 8px; color: var(--text); font-family: var(--mono); box-sizing: border-box;
}
.form-row input:focus, .form-row select:focus { border-color: var(--accent); outline: none; }
.form-row input[type="number"] { width: 8rem; }
.form-row select { min-width: 8rem; }
.form-check { justify-content: flex-start; }
.form-check input[type="checkbox"] { accent-color: var(--accent); }
.dialog-actions { display: flex; gap: .5rem; justify-content: flex-end; margin-top: .75rem; }
.btn {
  background: none; border: none; font: inherit; font-family: var(--mono);
  color: var(--subtext1); cursor: pointer; white-space: nowrap; padding: 2px 8px;
}
.btn:hover { background: var(--surface1); color: var(--text); }
.btn-primary { color: var(--green); }
.btn-primary:hover { background: var(--green); color: var(--crust); }

/* Slice 3: status bar. */
.app-main { display: flex; flex-direction: column; min-height: 0; flex: 1; }

/* Settings screen (toolbar 🎛 / `,`): left section list + a scrollable right
   body, inside a WModal (its width is `width=760`). Reuses the skin-A
   `.form-row`/`.dialog-actions`/`.btn` controls above. */
.settings-cols { display: flex; gap: 1rem; overflow: hidden; flex: 1; min-height: 0; }
.settings-sections { list-style: none; margin: 0; padding: 0; min-width: 9rem; border-right: 1px solid var(--surface1); overflow-y: auto; }
.settings-section-item { padding: .35rem .5rem; cursor: pointer; border-radius: 4px; color: var(--subtext1); }
.settings-section-item:hover { background: var(--surface0); }
.settings-section-item.active { background: var(--surface1); color: var(--text); }
.settings-body { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: .5rem; padding-right: .6rem; }
/* Subgroup heading inside a merged Settings section (mirrors the TUI's header rows). */
.wset-sub { margin: .9rem 0 .1rem; font-size: .78rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--accent); }
.wset-sub:first-child { margin-top: 0; }
.groups-list { list-style: none; margin: 0 0 .5rem; padding: 0; }
.group-row {
  display: grid;
  grid-template-columns: 12rem 1fr auto;
  align-items: center;
  gap: 1rem;
  padding: .25rem .4rem;
  cursor: pointer;
  border-radius: 4px;
}
.group-row .group-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.group-row .group-limits { color: var(--overlay1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.group-row:hover { background: var(--surface0); }
.group-row.active { background: var(--surface1); box-shadow: inset 2px 0 0 var(--accent); }
.group-row .row-del { background: none; border: none; color: var(--overlay0); cursor: pointer; opacity: 0; padding: 0 .25rem; }
.group-row:hover .row-del, .group-row.active .row-del { opacity: 1; }
.group-row .row-del:hover { color: var(--red); }
.group-limits { color: var(--subtext1); }
/* Live web-UI login sessions (Settings → Web & Serve): id prefix · address ·
   last seen · "current" marker · per-row Log out button. A grid so the columns
   line up down the list, overriding the flex `.form-row` it shares. */
.wsession-row { display: grid; grid-template-columns: 6rem 1fr 4rem 5rem auto; align-items: center; gap: .5rem; }
.wsession-row > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wsession-row .wsession-id { color: var(--subtext1); }
.wsession-row .wsession-ip, .wsession-row .wsession-seen { color: var(--overlay1); }
.wsession-row .wsession-current { color: var(--green); }
.group-editor { border-top: 1px solid var(--surface1); padding-top: .5rem; display: flex; flex-direction: column; gap: .5rem; }
/* Stable modal height so switching sections never resizes the modal (Categories/
   Groups registries grow past the fold otherwise); the registry list scrolls
   with its editor pinned at the bottom. The modal itself is fixed-height via
   `.wmodal.tall` (see below); `.settings-cols` just fills the space between
   `.wmhead` and `.wmfoot`. */
.reg-editor { display: flex; flex-direction: column; min-height: 0; flex: 1; }
.reg-editor .groups-list { flex: 1; min-height: 0; overflow-y: auto; margin-bottom: .5rem; }
.reg-editor .group-editor { flex: 0 0 auto; }
/* Always-visible styled scrollbar (do NOT set scrollbar-width — it disables
   these webkit rules in Chromium). */
.reg-editor .groups-list::-webkit-scrollbar { width: 10px; }
.reg-editor .groups-list::-webkit-scrollbar-track { background: var(--mantle); }
.reg-editor .groups-list::-webkit-scrollbar-thumb { background: var(--surface1); border-radius: 5px; border: 2px solid var(--mantle); }
.reg-editor .groups-list::-webkit-scrollbar-thumb:hover { background: var(--surface2); }

/* ── skin-A dialog primitives (R6) ─────────────────────────────────────── */
.scrim { position: fixed; inset: 0; background: rgba(17,17,27,.62); z-index: 40; }
.wmodal {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%,-50%);
  max-width: 92vw; max-height: 88vh; display: flex; flex-direction: column;
  background: var(--base); border: 1px solid var(--accent); z-index: 41;
  padding: 16px 18px 12px; font-family: var(--mono);
  box-shadow: 0 20px 60px rgba(0,0,0,.5);
}
.wmodal.tall { height: 86vh; }
.wmhead { display: flex; align-items: baseline; margin-bottom: 12px; }
.wmtitle { flex: 1; color: var(--accent); font-weight: 700; }
.wmbody { overflow-y: auto; display: flex; flex-direction: column; gap: 10px; }
.wmfoot {
  display: flex; gap: 14px; justify-content: flex-end;
  margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--surface1);
}
/* Why a Save was refused: pushed to the left of the buttons (the footer is
   right-aligned), wrapping instead of shoving them off the edge. */
.wmfoot-msg { margin-right: auto; align-self: center; text-align: left; min-width: 0; }
.wfield { display: flex; flex-direction: column; gap: 3px; }
.wfield > .t-ov { font-size: 11.5px; }
.winput {
  background: var(--mantle); border: 1px solid var(--surface1); border-radius: 0;
  padding: 3px 8px; color: var(--text); min-height: 24px; font: inherit;
  font-family: var(--mono); box-sizing: border-box;
}
input.winput, select.winput, textarea.winput { width: 100%; }
.winput:focus, .winput.on { border-color: var(--accent); outline: none; }
.wtog { display: flex; align-items: center; gap: 8px; padding: 2px 4px; cursor: pointer; }
.wtog:hover { background: var(--surface0); }
.wtog input[type="checkbox"] { accent-color: var(--accent); }
.wradio { display: flex; gap: 10px; align-items: flex-start; padding: 6px 6px; cursor: pointer; }
.wradio:hover { background: var(--surface0); }
.wradio.on { background: var(--surface1); box-shadow: inset 2px 0 0 var(--accent); }
.wradio .t-ov { font-size: 11.5px; }
.wdbtn {
  background: none; border: none; font: inherit; font-family: var(--mono);
  color: var(--subtext1); cursor: pointer; white-space: nowrap; padding: 2px 6px;
}
.wdbtn:hover { background: var(--surface1); color: var(--text); }
.wdbtn.green:hover { background: var(--green); color: var(--crust); }
.wdbtn.red { color: var(--red); }
.wdbtn.red:hover { background: var(--red); color: var(--crust); }
.wdbtn:disabled { color: var(--overlay0); pointer-events: none; }

/* ── session-stats modal (`t`) ─────────────────────────────────────────── */
/* the prototype's generic panel primitive, introduced by the stats modal */
.wpanel { background: var(--mantle); border: 1px solid var(--surface0); border-radius: 4px; padding: 8px 10px; }
.wstats { display: flex; flex-direction: column; gap: 12px; max-height: 70vh; overflow-y: auto; }
.wstats-tabs { display: flex; gap: 10px; align-items: center; }
.wstats-tab { color: var(--overlay1); cursor: pointer; }
.wstats-tab.on { color: var(--accent); font-weight: 600; }
.wstats-kpis { display: flex; gap: 12px; }
.wstats-kpis .wpanel { flex: 1; }
.wstats-kpi-label { font-size: 11px; color: var(--overlay1); }
.wstats-kpi-val { font-size: 18px; font-weight: 600; margin-top: 2px; }
.wstats-portfwd .kv .k { width: 120px; }
/* No top padding: the sticky `.dtbl-head` inside must sit flush at the scroll
   top, else rows peek through the panel's top padding while scrolling up. */
.wstats-nodes { max-height: 260px; overflow-y: auto; padding-top: 0; }

/* ── help modal (`?`) ──────────────────────────────────────────────────── */
.whelp { display: flex; flex-direction: column; gap: 12px; max-height: 70vh; overflow-y: auto; }
/* let the three panels divide into equal thirds (min-width:0 defeats flex's
   min-content floor) instead of the phrase-key Mouse panel overflowing the modal */
.whelp .wstats-kpis .wpanel { min-width: 0; }
.whelp .kv .k { width: 92px; text-align: right; flex: none; }
/* wrap long labels ("switch detail tab") under the key instead of clipping */
.whelp .kv .v { white-space: normal; }
/* peer-flags legend: two/three responsive columns instead of one long list */
.whelp-flags { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0 18px; }
/* DHT node table: ADDRESS · ID · STATE · AGE · FAILS · BUCKET (applied
   alongside .dtbl-head/.dtbl-row like the other per-tab grid templates). */
.dntbl { grid-template-columns: minmax(0, 1fr) 80px 64px 56px 56px 64px; }

/* ── folder browser (elevated second modal layer) ─────────────────────── */
/* Second modal layer: the folder browser stacks above a dialog. */
.scrim.hi { z-index: 60; }
.wmodal.hi { z-index: 61; }
.wdirs { display: flex; flex-direction: column; gap: 6px; max-height: 55vh; }
.wdirs-path { color: var(--overlay1); word-break: break-all; }
.wdirs-list { overflow-y: auto; display: flex; flex-direction: column; }
.wdirs-row { text-align: left; background: none; border: 0; cursor: pointer;
             color: var(--text); padding: 2px 4px; font: inherit; }
.wdirs-row:hover { color: var(--accent); }
/* Create-folder row: name input grows, the Create button stays its own width. */
.wdirs-new { display: flex; gap: 6px; align-items: center; }
.wdirs-new .winput { flex: 1; min-width: 0; }

/* ── Add-torrent dialog (fields + parsed metadata on the LEFT, per-file picker
   filling the RIGHT column full-height; intentionally diverges from the TUI) ── */
.wadd { min-height: 60vh; max-height: 80vh; overflow-y: auto; }
.wadd-cols { display: flex; gap: 14px; align-items: stretch; flex: 1; min-height: 0; }
.wadd-left { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
.wadd-toggles { display: flex; gap: 14px; flex-wrap: wrap; }
.wadd-right { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.wadd-file { display: flex; gap: 8px; align-items: center; }
.wadd-file .winput { flex: 1; }
.wadd-fname { flex: 1; color: var(--text); word-break: break-all; }
.wadd-preview { display: flex; flex-direction: column; gap: 1px; }
.wadd-preview .kv { padding: 0; }
.wadd-preview .kv .k { width: 88px; }
.wadd-placeholder { color: var(--overlay1); padding: 24px 8px; text-align: center; }
.wadd-files-head { display: flex; align-items: center; gap: 8px; padding: 2px 4px; }
.wadd-files {
  display: flex; flex-direction: column; flex: 1; min-height: 0;
  overflow-y: auto;
  border: 1px solid var(--surface0); border-radius: 4px;
}
.wadd-file-row {
  /* checkbox · name · size · priority; minmax(0,…) lets the name ellipsize */
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  gap: 10px; align-items: center;
  padding: 3px 8px; border-bottom: 1px solid var(--surface0);
}
.wadd-file-row:last-child { border-bottom: 0; }
.wadd-file-path {
  color: var(--subtext1);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── skin-A "terminal-refined": root wrapper + layout skeleton (R1) ──────────
   The app root carries `web skin-terminal` so ported prototype styles
   (design_handoff_torrent_web/prototype/web/web.css) apply. R1 lays down ONLY
   the shell/skeleton + tokens + font; each later slice (R2–R6) migrates its
   region's markup + styles and drops the corresponding pre-redesign rules. */
.web.skin-terminal {
  height: 100%;
  display: flex;
  flex-direction: column;
  min-height: 0;
  font-family: var(--mono);
  font-size: 12.5px;
  background: var(--base);
  color: var(--text);
}

/* flex-column content: toolbar / body / statusbar */
.web.skin-terminal .app-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* body row: fixed-width filters rail + main (list-over-details) column */
.wbody {
  flex: 1;
  display: flex;
  min-height: 0;
}
.web.skin-terminal .filters {
  flex: none;
  width: 210px;
  border-right: 1px solid var(--surface1);
}
.wmain {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}
.wlist-region {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.wlist-region > .list-pane {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.wdet-region {
  flex: none;
  min-height: 140px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.wdet-region > .detail-col {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.wdet-region .detail-pane {
  flex: 1;
  min-height: 0;
  margin-top: 0;
  display: flex;
  flex-direction: column;
}
.wdet-region .detail-body {
  flex: 1;
  min-height: 0;
  /* Scroll on THIS stable element (it lives in DetailPane, OUTSIDE the per-tick
     tab remount), so scrollTop survives the ~4 Hz tab re-render — scrolling on a
     child that remounts would snap back to 0 every frame. A flex column so the
     tab shell can fill it and pin its footer at the bottom. */
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
/* Per-tab shell: fills the panel (so a SHORT tab still pushes its footer to the
   bottom instead of floating mid-panel), header+list on top, footer pinned
   below. (NOT `.wtab` — that is the tab-strip BUTTON class; a collision made the
   buttons flex.) */
.wdet-tab {
  flex: 1 0 auto;
  min-height: 100%;
  display: flex;
  flex-direction: column;
}
/* Header+list region: grows to fill the shell (short → footer at the bottom) or
   to its content (long → the stable .detail-body scrolls). Deliberately does
   NOT scroll itself — that would reset scrollTop on every remount. */
.wtab-scroll {
  flex: 1 0 auto;
}

/* draggable divider (drag wired in Task 4) */
.wdivider {
  height: 8px;
  flex: none;
  cursor: row-resize;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--mantle);
  border-top: 1px solid var(--surface1);
  border-bottom: 1px solid var(--surface1);
  touch-action: none;
}
.wdivider:hover { background: var(--surface0); }
.wdivider:hover .wdiv-grip { background: var(--accent); }
.wdiv-grip {
  width: 44px;
  height: 3px;
  border-radius: 2px;
  background: var(--surface2);
}

/* brand chip in the toolbar (moved out of the removed <header>) */
.wbrand {
  background: var(--accent);
  color: var(--crust);
  font-weight: 700;
  padding: 2px 10px;
  border-radius: 3px;
  margin-right: 6px;
}

/* connection dot in the status bar (moved out of the removed <header>) */
.sb-conn { color: var(--overlay0); }
.sb-conn::before { content: "●"; margin-right: 0.35rem; color: var(--overlay0); }
.sb-conn.online, .sb-conn.online::before { color: var(--green); }

/* narrow viewport: stack filters above the list-over-details column */
@media (max-width: 640px) {
  .wbody { flex-direction: column; }
  .web.skin-terminal .filters {
    width: auto;
    border-right: none;
    border-bottom: 1px solid var(--surface0);
    max-height: 10rem;
  }
  .app-main.has-selection .filters { display: none; }
  .wdivider { display: none; }
  .app-main.has-selection .wlist-region { display: none; }
  .app-main:not(.has-selection) .wdet-region { display: none; }
  .app-main.has-selection .wdet-region { height: auto !important; flex: 1; }
}

/* ── skin-A chrome: toolbar buttons + search (R2) ─────────────────────────── */
.wtoolbar {
  display: flex;
  align-items: center;
  gap: 1px;
  flex: none;
  padding: 6px 10px;
  border-bottom: 1px solid var(--surface1);
  background: var(--mantle);
}
.wbtn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  white-space: nowrap;
  color: var(--subtext1);
  padding: 3px 7px;
  border-radius: 3px;
  font: inherit;
  font-size: 12px;
  border: 0;
  background: none;
}
.wbtn:hover { background: var(--surface0); }
.wbtn:disabled,
.wbtn.dis { opacity: 0.4; pointer-events: none; }
.wbtn .ic {
  width: 16px;
  height: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.wbtn .key { color: var(--overlay0); font-size: 11px; }
.wbtn.tb-log.has-unread { color: var(--peach); }
.key {
  background: var(--surface1);
  color: var(--text);
  border-radius: 2px;
  padding: 0 4px;
  font-size: 11px;
  font-weight: 700;
}
.wsearch {
  width: 30px;
  overflow: hidden;
  background: var(--surface0);
  border: 1px solid var(--surface1);
  border-radius: 3px;
  padding: 2px 7px;
  color: var(--overlay1);
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--mono);
  font-size: 12px;
  cursor: text;
  transition: width 0.18s ease, border-color 0.12s ease;
  flex: none;
  margin-right: 4px;
}
.wsearch .ic { flex: none; }
.wsearch input {
  border: 0;
  background: transparent;
  color: var(--text);
  outline: none;
  width: 100%;
  font: inherit;
  font-family: var(--mono);
}
.wsearch input::placeholder { color: var(--overlay0); }
.wsearch:hover { border-color: var(--surface2); }
.wsearch:hover,
.wsearch:focus-within { width: 220px; }
.wsearch:focus-within { border-color: var(--accent); color: var(--text); }
/* color helpers for toolbar glyphs (subset of prototype `.t-*`) */
.t-peach { color: var(--peach); }
.t-green { color: var(--green); }
.t-yellow { color: var(--yellow); }
.t-red { color: var(--red); }
.t-blue { color: var(--blue); }
.t-text { color: var(--text); }
/* muted-text helpers (prototype `.t-sub`/`.t-sub1`/`.t-ov`, R3: the list's
   row-number/SIZE/S-P columns and the head's shown-count/search note). */
.t-sub { color: var(--subtext0); }
.t-sub1 { color: var(--subtext1); }
.t-ov { color: var(--overlay0); }
/* idle-rate muting (Peers ↓/↑ cells when a peer isn't currently sending). */
.dim { color: var(--overlay1); }

/* Shared destructive-button accent (used by the Settings Groups "Delete"
   button, `settings.rs`). The old toolbar's `.tb-danger` rule was dropped when
   the toolbar migrated to `.wbtn`; re-provided here until Settings gets its own
   skin-A restyle (R6). */
.btn.tb-danger:not([disabled]) { color: var(--red); border-color: var(--red); }

/* ── skin-A chrome: filters panel (R2) ────────────────────────────────────── */
.web.skin-terminal .filters {
  padding: 8px 6px;
  background: var(--mantle);
  overflow-y: auto;
}
.wgrp {
  font-size: 10px;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--overlay0);
  text-transform: uppercase;
  margin: 8px 4px 2px;
}
.wfrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
  padding: 2px 8px;
  border-radius: 3px;
}
.wfrow:hover { background: var(--surface0); }
.wfrow.on { background: var(--surface1); box-shadow: inset 2px 0 0 var(--accent); }
.wfrow .lbl { display: flex; align-items: center; gap: 8px; min-width: 0; }
.wfrow .lbl span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wfrow .cnt { color: var(--overlay0); font-family: var(--mono); font-size: 11px; flex: none; }
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 1px; flex: none; }

/* ── skin-A chrome: status bar (R2) ───────────────────────────────────────── */
.web.skin-terminal .statusbar {
  display: flex;
  align-items: center;
  gap: 0;
  padding: 0;
  border-top: 1px solid var(--surface1);
  background: var(--mantle);
  font-size: 11.5px;
  color: var(--subtext0);
}
.wseg { padding: 3px 10px; font-weight: 700; }
.sb-item { padding: 0 10px; }
.sb-sep { color: var(--overlay0); }
.sb-spacer { margin-left: auto; }

/* Status-bar fixed-width numerics: pin the mutable readouts so the bar never
   reflows as rates/sizes change width (bug #1). tabular-nums keeps digits from
   jittering; the fixed ch-widths (mono font) cap the widest rate/size string so
   the segment never grows — min-width only reserved a floor and still grew. */
.sb-conn { margin-right: 0.5rem; }
.sb-rate { display: inline-block; width: 13ch; text-align: right; }
.sb-size { display: inline-block; width: 10ch; text-align: right; }
.statusbar .sb-item { font-variant-numeric: tabular-nums; }

/* ── skin-A transfers list (R3) ───────────────────────────────────────────── */
.web.skin-terminal .wlist {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  border-bottom: 1px solid var(--surface1);
}
.wlist-head {
  display: flex;
  align-items: center;
  padding: 5px 12px;
  border-bottom: 1px solid var(--surface1);
  background: var(--mantle);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--overlay0);
  font-weight: 700;
  /* Keep the "↓ rate  ↑ rate" totals on one line — inherits to the fixed-width
     .sb-rate spans too, so a wide value can't wrap the header to two rows (#6). */
  white-space: nowrap;
}
.wstrip,
.wrow {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) 78px 150px 72px 64px 78px 92px;
  align-items: center;
}
/* SIZE uses the full `format::size` ("1023.0 KiB") — keep it on one line so a
   verbose value doesn't wrap and break the dense uniform row height. */
.wcell-size { white-space: nowrap; }
.wstrip {
  padding: 5px 12px;
  border-bottom: 1px solid var(--surface1);
  background: var(--mantle);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--overlay0);
  font-weight: 700;
}
.wlist-rows {
  flex: 1;
  overflow-y: auto;
  min-height: 0;
}
.wrow {
  padding: 3px 12px;
  border-bottom: 1px solid rgba(69, 71, 90, 0.35);
  cursor: pointer;
}
.wrow:hover { background: var(--surface0); }
.wrow.sel { background: var(--surface1); }
.wcol-r { text-align: right; }
.num { font-variant-numeric: tabular-nums; }
.b { font-weight: 700; }
.wrow-empty { padding: 10px 12px; color: var(--overlay0); }
.wname {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
}
.wstatus-cell {
  overflow: hidden;
  white-space: nowrap;
  padding-left: 10px;
}
.wprog {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-left: 6px;
}
.pbar {
  position: relative;
  height: 12px;
  background: var(--surface0);
  border-radius: 0;
  overflow: hidden;
  flex: 1;
}
.pfill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 0;
}
/* narrow viewport: drop SIZE + S/P (keep # NAME PROGRESS ↓ ↑ STATUS) */
@media (max-width: 640px) {
  .wstrip,
  .wrow {
    grid-template-columns: 26px minmax(0, 1fr) 150px 72px 64px 92px;
  }
  .wcell-size,
  .wcell-sp {
    display: none;
  }
}

/* ── skin-A detail tabs (R4) ──────────────────────────────────────────────── */
.wtabs {
  display: flex;
  flex: none;
  padding: 0 10px;
  gap: 0;
  border-bottom: 1px solid var(--surface1);
  background: var(--mantle);
}
.wtab {
  cursor: pointer;
  white-space: nowrap;
  color: var(--overlay1);
  font-weight: 500;
  padding: 5px 12px;
  background: none;
  border: 0;
  font: inherit;
  font-family: var(--mono);
}
.wtab:hover { color: var(--text); }
.wtab.on { background: var(--accent); color: var(--crust); font-weight: 700; }
.wtab .key { opacity: 0.6; margin-right: 4px; }

/* key/value rows (General) */
.kv { display: flex; gap: 10px; padding: 2px 0; min-width: 0; }
.kv .k { color: var(--overlay0); flex: none; width: 116px; }
.kv .v {
  color: var(--subtext1);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* detail grid tables (Files/Trackers/Peers) */
.dtbl-head {
  display: grid;
  align-items: center;
  gap: 0;
  padding: 3px 6px;
  border-bottom: 1px solid var(--surface1);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  color: var(--overlay0);
  font-weight: 700;
  text-transform: uppercase;
  position: sticky;
  /* `.detail-body` has no vertical padding, so the header sits flush at the
     scroll-area top at rest and sticks flush there while scrolling. */
  top: 0;
  /* a sticky element keeps z-index auto, so later rows (same stacking context)
     paint OVER it while scrolling — lift the header above them. */
  z-index: 2;
  background: var(--base);
}
.dtbl-row {
  display: grid;
  align-items: center;
  gap: 0;
  padding: 2px 6px;
  border-bottom: 1px solid rgba(69, 71, 90, 0.28);
  cursor: pointer;
}
.dtbl-row:hover { background: var(--surface0); }
.dtbl-row.sel { background: var(--surface1); box-shadow: inset 2px 0 0 var(--accent); }
/* Lingering (recently-dropped) peer the daemon is holding for a few seconds
   before removing it, so the list doesn't flicker (#8) — greyed out. */
.dtbl-row.pstale { opacity: 0.45; }
.dhint { font-size: 11px; color: var(--overlay0); margin-top: 6px; }
.dhint .key { font-family: var(--mono); }
/* A tab's bottom hint/legend, glued to the panel bottom. On SHORT content the
   `.wtab-scroll` above it flex-grows and pushes it down; on a LONG list the
   `.detail-body` scrolls and `position:sticky; bottom:0` pins it flush to the
   viewport bottom (opaque bg + z-index so rows don't bleed through). `.detail-body`
   has no vertical padding, so it sits flush at rest too. A top rule separates it. */
.wdet-foot {
  flex: none;
  position: sticky;
  bottom: 0;
  z-index: 2;
  border-top: 1px solid var(--surface1);
  padding-top: 5px;
  margin-top: 0;
  background: var(--base);
}

/* per-tab grid column templates (applied alongside .dtbl-head/.dtbl-row) */
/* NAME · SIZE · PROGRESS · DONE · PRIORITY · AVAIL — SIZE/DONE sized so the
   widest byte string ("1023.9 MiB") fits on one line (see .num nowrap below). */
.ftbl { grid-template-columns: minmax(0, 1fr) 82px 150px 82px 104px 58px; }
.tktbl { grid-template-columns: minmax(0, 1fr) 140px 60px 66px 66px; }
/* CC · ADDRESS · CLIENT (both fixed) · FLAGS · PROGRESS (stretches) · ↓ · ↑ */
.ptbl { grid-template-columns: 46px 176px 225px 88px minmax(0, 1fr) 60px 60px; }

/* Files: folder header row (not a grid row), priority controls */
.dfolder { color: var(--mauve); padding: 2px 6px; cursor: pointer; user-select: none; }
.dfolder:hover { background: var(--surface0); }
/* Files NAME cell: checkbox + ellipsized name. Only the checkbox toggles
   download (the name is a plain sibling, not inside a <label>). */
.dfile { display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; }
.dchk { accent-color: var(--green); flex: none; cursor: pointer; margin: 0; }
.dfname { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Priority cell: `− word +` centered as one group with small gaps, so the
   controls hug the word (not spread to the column edges). The word sits in a
   fixed-width slot, so the `−`↔`+` distance is constant across rows. */
.dprio { display: flex; align-items: center; justify-content: center; gap: 6px; }
.dprio-label { width: 52px; text-align: center; white-space: nowrap; }
.dprio-btn {
  padding: 0;
  background: none;
  border: 0;
  color: var(--overlay1);
  cursor: pointer;
  font: inherit;
}
.dprio-btn:hover { color: var(--text); }
/* numbers never wrap: keeps a long DONE/SIZE/AVAIL ("323.9 KiB") on one line so
   a file row stays one dense line instead of doubling in height. */
.ftbl > span.num { white-space: nowrap; }
.wcol-c { text-align: center; }
.dcell { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* color helpers the detail tabs need that earlier slices didn't add yet */
.t-teal { color: var(--teal); }
.t-mauve { color: var(--mauve); }

/* ── Context menu + copy toast ─────────────────────────────────────────── */
.wctx-backdrop { position: fixed; inset: 0; z-index: 79; }
.wctxmenu {
  position: fixed; z-index: 80;
  background: var(--surface0); color: var(--text);
  border: 1px solid var(--surface1); border-radius: 6px;
  padding: 4px; font-size: 12px; box-shadow: 0 6px 24px rgba(0,0,0,.35);
  user-select: none;
}
.wctx-item {
  display: flex; align-items: center;
  padding: 4px 10px; border-radius: 4px; cursor: pointer;
  white-space: nowrap; overflow: hidden;
}
.wctx-ic { flex: none; width: 1.4em; margin-right: 6px; text-align: center; color: var(--overlay1); }
.wctx-item:hover { background: var(--surface1); }
.wctx-item.danger { color: var(--red); }
.wctx-item.danger .wctx-ic { color: var(--red); }
.wctx-item.danger:hover { background: var(--red); color: var(--crust); }
.wctx-item.danger:hover .wctx-ic { color: var(--crust); }
.wctx-sep { height: 1px; background: var(--surface1); margin: 4px 6px; }
.wctx-section {
  padding: 4px 10px 2px; font-size: 10px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--overlay0);
}
.wtoast {
  position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%);
  z-index: 90; background: var(--surface1); color: var(--text);
  border: 1px solid var(--surface2); border-radius: 6px; padding: 6px 14px;
  font-size: 12px; box-shadow: 0 4px 16px rgba(0,0,0,.3);
}

/* Peers tab CC column (vendored flag-icons SVG + code) and the selected
   peer's geo line above .wdet-foot. */
.cc-cell { display: inline-flex; align-items: center; gap: 3px; }
.cc-flag { width: 16px; height: 12px; object-fit: cover; border-radius: 1px; vertical-align: middle; }
.wtip {
  position: fixed; z-index: 78; pointer-events: none;
  background: var(--surface1); color: var(--text);
  border: 1px solid var(--surface2); border-radius: 4px;
  padding: 3px 8px; font-size: 11px; line-height: 1.3;
  white-space: nowrap; box-shadow: 0 3px 10px rgba(0,0,0,.4);
}

/* Speed value + unit picker (SpeedLimitInput): a number box + KiB/MiB/GiB select. */
.wspeed { display: flex; gap: .35rem; align-items: center; min-width: 0; }
/* `input.`/`select.` qualifiers so these beat `.form-row input[type=number]`
   (0,2,1) and `.form-row select` (0,1,1), which otherwise force the number to
   8rem and the unit select to a 8rem min-width in the Settings context. */
.wspeed input.wspeed-num { flex: 1 1 0; width: auto; min-width: 0; }
.wspeed select.wspeed-unit { flex: 0 0 4.2rem; width: 4.2rem; min-width: 0; }

/* Chip-style tag editor (TagInput): committed chips + an inline input with a
   custom per-active-token suggestion dropdown (replaces a native <datalist>,
   which can't scope to just the active token). */
.wtags { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; border: 1px solid var(--surface1); border-radius: 4px; padding: 3px 4px; background: var(--mantle); }
.wtags:focus-within { border-color: var(--accent); }
.wchip { display: inline-flex; align-items: center; gap: 3px; background: var(--surface1); color: var(--text); border-radius: 3px; padding: 1px 4px; font-size: .85em; white-space: nowrap; }
.wchip-x { background: none; border: none; color: var(--overlay1); cursor: pointer; padding: 0; line-height: 1; }
.wchip-x:hover { color: var(--red); }
.wtags-inwrap { position: relative; flex: 1; min-width: 6rem; }
.wtags-input { border: none; background: transparent; width: 100%; padding: 1px 2px; }
.wtags-input:focus { outline: none; border: none; }
.wtags-ph { position: absolute; left: 4px; top: 2px; color: var(--overlay0); pointer-events: none; }
/* Body-portaled (see TagInput's <Portal>): position:fixed with left/top set
   inline from the input's getBoundingClientRect(), so it overlays on top of
   the dialog instead of being clipped by `.wmbody { overflow-y: auto }` or
   mispositioned by `.wmodal`'s transform. z-index 90 matches the other
   body-level overlay layer (`.wctx-menu`). */
.wtags-menu { position: fixed; z-index: 90; margin: 2px 0 0; padding: 2px; list-style: none; min-width: 8rem; max-height: 12rem; overflow-y: auto; background: var(--base); border: 1px solid var(--surface1); border-radius: 4px; box-shadow: 0 4px 12px rgba(0,0,0,.35); }
.wtags-opt { padding: 2px 6px; border-radius: 3px; cursor: pointer; }
.wtags-opt:hover, .wtags-opt.active { background: var(--surface1); }
