/* ibm-plex-mono-cyrillic-ext-400-normal */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(./ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2) format('woff2'), url(./ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff) format('woff');
  unicode-range: U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F;
}

/* ibm-plex-mono-cyrillic-400-normal */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(./ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2) format('woff2'), url(./ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff) format('woff');
  unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
}

/* ibm-plex-mono-vietnamese-400-normal */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(./ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2) format('woff2'), url(./ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff) format('woff');
  unicode-range: U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB;
}

/* ibm-plex-mono-latin-ext-400-normal */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(./ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2) format('woff2'), url(./ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff) format('woff');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

/* ibm-plex-mono-latin-400-normal */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(./ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2) format('woff2'), url(./ibm-plex-mono-latin-400-normal-CvHOgSBP.woff) format('woff');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}/* ibm-plex-mono-cyrillic-ext-600-normal */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 600;
  src: url(./ibm-plex-mono-cyrillic-ext-600-normal-V-xxqcpd.woff2) format('woff2'), url(./ibm-plex-mono-cyrillic-ext-600-normal-9HEixskS.woff) format('woff');
  unicode-range: U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F;
}

/* ibm-plex-mono-cyrillic-600-normal */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 600;
  src: url(./ibm-plex-mono-cyrillic-600-normal-CTOM6hUh.woff2) format('woff2'), url(./ibm-plex-mono-cyrillic-600-normal-fLZuRloM.woff) format('woff');
  unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
}

/* ibm-plex-mono-vietnamese-600-normal */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 600;
  src: url(./ibm-plex-mono-vietnamese-600-normal-D2EvbN8M.woff2) format('woff2'), url(./ibm-plex-mono-vietnamese-600-normal-iLQfcSjf.woff) format('woff');
  unicode-range: U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB;
}

/* ibm-plex-mono-latin-ext-600-normal */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 600;
  src: url(./ibm-plex-mono-latin-ext-600-normal-D38SheWl.woff2) format('woff2'), url(./ibm-plex-mono-latin-ext-600-normal-DmB0ttJJ.woff) format('woff');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

/* ibm-plex-mono-latin-600-normal */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 600;
  src: url(./ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2) format('woff2'), url(./ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff) format('woff');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}:root {
  color-scheme: dark;
  font: 13px/1.4 system-ui, -apple-system, sans-serif;

  /* Every grey is exactly neutral (R = G = B). A blue- or warm-tinted chrome
     shifts the perceived hue of the render sitting next to it, which is the
     one thing this app must never do. See docs/superpowers/specs/
     2026-08-23-obsrv-ui-style.md. */
  --void: #000000;
  --chrome-0: #141414;
  --chrome-1: #1c1c1c;
  --chrome-2: #262626;
  --chrome-3: #3a3a3a;
  --line: #333333;
  --text-0: #ededed;
  --text-1: #8a8a8a;
  /* Text fields sit a step below the chrome so their contents read as content. */
  --field: #0d0d0d;

  /* The only chromatic values in the whole chrome. Colour means attention,
     never decoration — there is deliberately no brand accent. */
  --warn: #d8a33a;
  --error: #e5484d;

  /* Overwritten by the surround control; the pane field only. */
  --surround: #2a2a2a;

  --mono: 'IBM Plex Mono', ui-monospace, monospace;
}

:root[data-surround='black'] { --surround: #000000; }
:root[data-surround='graphite'] { --surround: #2a2a2a; }
:root[data-surround='grey50'] { --surround: #808080; }

html, body, #root { margin: 0; height: 100%; background: var(--chrome-0); color: var(--text-0); }
/* `--split` (the native pane's share), `--pane-min` and `--seam` live here
   rather than on `.panes` because the chrome needs them too — see
   `--native-right` below. `App` writes the first two inline; the fallbacks
   keep the layout sane if either is ever missing. */
.app {
  height: 100%;
  display: flex;
  flex-direction: column;
  --split: 0.5;
  --pane-min: 240px;
  --seam: 1px;
  --drawer-w: 0px;
}

/* The drawer's whole box. Declared here so the panes row's width can be
   derived from the window's without measuring either; `.drawer` sizes itself
   from the same value, so the two cannot drift. */
/* The drawer's width is the one number the layout derives from — the aside's
   basis, the panes' widths, the URL-history clamp — so it is the one thing
   that animates: a registered property transitions, and everything follows. */
@property --drawer-w {
  syntax: '<length>';
  inherits: true;
  initial-value: 0px;
}
.app { transition: --drawer-w 220ms ease; }
.app[data-drawer='panel'] { --drawer-w: 309px; }
@media (prefers-reduced-motion: reduce) {
  .app, .url-progress, .url-progress-bar { transition: none; }
}

/* Where the native pane's right edge falls, in window coordinates: the very
   clamp `.panes > .pane:first-child` is laid out with, applied to the width
   the row gets (the window, less whatever a drawer takes). A style rule, not
   a measurement — the split is already a custom property, so nothing here
   observes the layout it is describing.
   Solo target has no native view, so the clamp collapses to zero. */
.app[data-panes='both'] {
  --native-right: round(
    down,
    clamp(
      var(--pane-min),
      calc((100vw - var(--drawer-w) - var(--seam)) * var(--split)),
      calc(100vw - var(--drawer-w) - var(--seam) - var(--pane-min))
    ),
    1px
  );
}
.app[data-panes='target'] { --native-right: 0px; }
.muted { color: var(--text-1); }
.warn { color: var(--warn); }
.chrome .warn { white-space: nowrap; }

/* Three strips. The tab strip on top — furthest from the panes, as every
   browser puts it; browsing below it; the controls that describe the simulated
   screen last, on a darker ground so the rows read as separate registers.
   32 + 44 + 38 = 114 must equal TOOLBAR_H in src/main/ipc.ts. Pinned heights
   also keep a late reflow from resizing the panes under NativeSlot's
   observer. */
.chrome { flex: 0 0 auto; }
.chrome-row {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 10px;
  border-bottom: 1px solid var(--line);
}
.chrome-browse { height: 44px; background: var(--chrome-1); }
/* The containing block the URL-history list falls back to. Its padding box
   starts at the window's left edge (the row's only border is at the bottom),
   which is the frame `--native-right` is expressed in. */
.chrome-browse { position: relative; }
/* The screen controls sit centred: they describe the render, and the render is
   what the two panes flank, so the middle is the shortest reach from either
   side. The AGENT chip is taken out of flow rather than balanced against a
   spacer — otherwise the group would shift sideways the moment agent control
   was switched on, and a control that moves when an unrelated setting changes
   is worse than one slightly off-centre. */
.chrome-screen { height: 38px; background: var(--chrome-0); justify-content: center; position: relative; }
/* The row's tail: the agent chip and the drawer's toggle, out of flow so
   their presence cannot shift the centred controls, at the edge the drawer
   opens from. */
.chrome-screen .screen-tail { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); display: flex; align-items: center; gap: 8px; }
.icon-button[aria-pressed='true'] { background: var(--chrome-3); border-color: var(--text-1); }
.chrome-spacer { flex: 1 1 auto; }

/* The tab strip. Ground is the darkest chrome step so an inactive tab (one
   step up) already reads as a raised object, and the active one has somewhere
   further to go. Neutral throughout: a coloured tab beside the panes would
   shift the perceived hue of the render under test, which is the one thing
   this chrome may never do. */
.chrome-tabs { height: 32px; gap: 4px; padding: 0 6px; background: var(--chrome-0); }
/* The tabs scroll rather than shrink past legibility; the new-tab button is
   outside this box so it never scrolls away from the pointer. */
.tabs { display: flex; align-items: center; gap: 4px; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.tabs::-webkit-scrollbar { display: none; }

.tab {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 180px;
  height: 24px;
  display: flex;
  align-items: center;
  background: var(--chrome-1);
  border: 1px solid var(--line);
  border-radius: 4px;
}
.tab-label {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  padding: 0 2px 0 8px;
  background: none;
  border: 0;
  color: var(--text-1);
  font: inherit;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
.tab:hover { background: var(--chrome-2); }
/* The chrome's one selected idiom: the `--chrome-3` fill step plus weight,
   exactly as `.segmented button[aria-pressed]` and `.settings-nav button.on`
   mark theirs. Selection is carried by the label button's `aria-selected`, so
   the fill is derived from it rather than duplicated onto the wrapper. */
.tab:has(> .tab-label[aria-selected='true']) { background: var(--chrome-3); border-color: var(--chrome-3); }
.tab > .tab-label[aria-selected='true'] { color: var(--text-0); font-weight: 600; }

/* The driven tab, while agent control is on. The 2px inset rule the nav
   and `.surround-control` already use to mark a choice, on the leading edge so
   it never competes with the selected fill underneath it.

   It wears the warn hue for as long as an agent holds the tab, selected or
   not, so a background tab being worked on is visible without switching to it.
   Hue is tolerable here because the tab strip is the row furthest from the
   panes — and nowhere nearer them. No blur: a glow beside a render would spill
   light onto the pixels being measured. */
/* Dragging: the tab being carried fades, and a 2px rule marks where it lands.
   The rule is on the left edge of the tab under the pointer, which is where
   the dragged tab is inserted — a highlight on the whole tab would say
   "swap with this one", which is not what happens. 2px because 1px at 1x on a
   cheap panel is the hairline this product exists to complain about. */
.tab.dragging { opacity: 0.4; }
.tab.drop-before { box-shadow: inset 2px 0 0 var(--text-0); }

.tab.driven { box-shadow: inset 2px 0 0 var(--warn); background: color-mix(in srgb, var(--warn) 14%, var(--chrome-1)); }
.tab.driven:has(> .tab-label[aria-selected='true']) { background: color-mix(in srgb, var(--warn) 22%, var(--chrome-3)); }
.tab.driven > .tab-label { color: var(--text-0); }
/* An arriving command: the rule brightens to the full hue for ~3s. */
.tab.driven.busy { box-shadow: inset 3px 0 0 var(--warn); }

.tab-close {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  margin-right: 3px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 3px;
  color: var(--text-1);
  cursor: pointer;
}
/* Only on hover of the tab: sixteen ✕ glyphs at rest is a busier strip than
   the titles they sit beside, and the titles are the content here. */
.tab-close { opacity: 0; }
.tab:hover .tab-close,
.tab:has(> .tab-label[aria-selected='true']) .tab-close,
.tab-close:focus-visible { opacity: 1; }
.tab-close:hover { background: var(--chrome-2); color: var(--text-0); }

.tab-new {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: var(--chrome-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--text-0);
  cursor: pointer;
}
.tab-new:hover:not(:disabled) { background: var(--chrome-3); }
/* At the cap. Dimmed rather than hidden, and the `title` says why and where to
   raise it — a control that vanishes teaches nothing. */
.tab-new:disabled { color: var(--text-1); background: var(--chrome-1); cursor: default; }

/* No blanket `button` rule here, ever. The old `.toolbar button { width:
   26px }` was specificity (0,1,1) and silently clipped the update button to
   "v0."; every control now sizes itself. */
.icon-button {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--chrome-2);
  color: var(--text-0);
  border: 1px solid var(--line);
  border-radius: 5px;
  cursor: pointer;
}

/* Static in Both, so the history list below resolves against `.chrome-browse`
   and can be placed in window coordinates. In solo target there is no native
   view to dodge and the list belongs under the field, so the form becomes the
   containing block instead. */
.url-form { position: static; flex: 1 1 auto; min-width: 160px; }
.app[data-panes='target'] .url-form { position: relative; }
.url-form input {
  width: 100%;
  height: 30px;
  box-sizing: border-box;
  padding: 0 10px;
  font-size: 12.5px;
  background: var(--field);
  color: var(--text-0);
  border: 1px solid var(--line);
  border-radius: 5px;
}
.url-form input[readonly] { color: var(--text-1); }

/* Chrome's loading strip, inside the field: a hairline along the bottom
   edge, inset past the border and the corner curve, that creeps towards the
   right while the target loads and completes when it stops. The creep is
   one long transition (Chromium reports no progress figure); `done` is a
   short one to the end plus a fade. Reduced motion drops the transitions:
   the strip still appears and goes, it just does not move. */
.url-field { position: relative; }
.url-progress {
  position: absolute;
  left: 2px;
  right: 2px;
  bottom: 1px;
  height: 2px;
  overflow: hidden;
  pointer-events: none;
}
.url-progress-bar {
  height: 100%;
  background: var(--text-0);
  transform-origin: left;
  transform: scaleX(0);
  transition: transform 15s cubic-bezier(0.05, 0.7, 0.1, 1);
}
.url-progress[data-go] .url-progress-bar { transform: scaleX(0.9); }
.url-progress[data-phase='done'] .url-progress-bar { transform: scaleX(1); transition: transform 160ms ease-out; }
.url-progress[data-phase='done'] { opacity: 0; transition: opacity 200ms ease 160ms; }

/* Visited-URL type-ahead. Its left edge is clamped to the native pane's right
   edge because that pane is an OS-level WebContentsView painting over
   anything the renderer draws beneath it: a list anchored to the field, which
   starts near x=120, would be entirely invisible. The cost is cosmetic — with
   a wide native pane the list sits offset from the field it belongs to — and
   it is the same constraint that pushed the overflow menu rightward.
   `right` bounds it against the window so a very wide split cannot push it
   off the edge; `max-width` keeps it from spanning a whole empty pane. */
.url-history {
  position: absolute;
  top: calc(100% + 4px);
  left: var(--native-right, 0px);
  right: 10px;
  max-width: 560px;
  z-index: 10;
  margin: 0;
  padding: 4px 0;
  list-style: none;
  font-size: 11.5px;
  background: var(--chrome-2);
  border: 1px solid var(--line);
  border-radius: 6px;
}
/* Solo target: the list is the field's own dropdown, so it tracks its width. */
.app[data-panes='target'] .url-history { left: 0; right: 0; max-width: none; }
.url-history-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  height: 26px;
  padding: 0 11px;
  cursor: pointer;
}
/* The chrome's one selected idiom, shared with `.settings-nav button.on`. */
.url-history-row.active { background: var(--chrome-3); box-shadow: inset 2px 0 0 var(--text-0); }
/* Truncated at the *start*, not the end. What distinguishes one of these
   addresses from another is the tail — the port, the path, the filename —
   and right-truncating a list of localhost URLs or file paths yields six
   identical rows, which is what the first screenshot of this list showed.
   `direction: rtl` puts the ellipsis on the leading edge; the `bdi` around
   the URL keeps the characters themselves in left-to-right order. */
.url-history-url {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  direction: rtl;
  /* The box is right-to-left only to move the ellipsis; a URL short enough to
     fit must still start at the left edge like every other row. */
  text-align: left;
  font-family: var(--mono);
}
.url-history-age { flex: 0 0 auto; color: var(--text-1); font-size: 10px; }

.status-cluster { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; font-size: 10px; }
.badge-error {
  color: var(--error);
  border: 1px solid var(--error);
  border-radius: 4px;
  padding: 2px 6px;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
/* An update is neither a warning nor an error, so it carries no colour. */
.update-button {
  flex: 0 0 auto;
  height: 30px;
  padding: 0 10px;
  white-space: nowrap;
  font-family: var(--mono);
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  background: var(--chrome-2);
  color: var(--text-0);
  border: 1px solid var(--line);
  border-radius: 5px;
  cursor: pointer;
}

/* Our surface, the platform's popup: the real select sits transparent on
   top, keeping the native menu, keyboard handling and Playwright's
   `selectOption` working unchanged. */
.select-shell { position: relative; flex: 0 0 auto; display: inline-flex; }
/* The trigger carries the surface the shell used to: it is a real button now,
   not a transparent native select sitting on top of a painted div. */
.select-trigger {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  height: 30px;
  box-sizing: border-box;
  padding: 0 26px 0 9px;
  font: inherit;
  font-size: 11.5px;
  white-space: nowrap;
  background: var(--chrome-2);
  color: var(--text-0);
  border: 1px solid var(--line);
  border-radius: 5px;
  cursor: pointer;
}
.select-trigger:hover { background: var(--chrome-3); }
/* The stack that fixes the width: one cell, every label in it, only the chosen
   one visible. `visibility` rather than `display: none` because a hidden box
   still has to take its width for this to work at all. */
.select-label { display: grid; align-items: center; }
.select-label > * { grid-area: 1 / 1; white-space: nowrap; text-align: left; }
.select-label-sizer { visibility: hidden; }
.select-trigger:focus-visible { outline: none; border-color: var(--text-1); }
.select-shell .select-chevron {
  position: absolute;
  right: 7px;
  display: flex;
  color: var(--text-1);
  pointer-events: none;
}

/* The menu. Fixed, so it is positioned against the window rather than against
   whatever the toolbar's overflow happens to be, and clamped in JS to the
   window's box — the whole point of replacing the native popup, which drew
   outside the app entirely. */
.select-menu {
  position: fixed;
  z-index: 60;
  /* Or `max-height` would bound the *content* box and the padding and border
     would sit outside it — the menu overflowed the window's bottom edge by
     exactly those 10px, which is the one thing this control exists to prevent. */
  box-sizing: border-box;
  min-width: 220px;
  padding: 4px;
  overflow-y: auto;
  overscroll-behavior: contain;
  font-size: 11.5px;
  background: var(--chrome-1);
  color: var(--text-0);
  border: 1px solid var(--line);
  border-radius: 6px;
  /* The one shadow in the chrome. A menu is the only surface that genuinely
     floats above the rest, and without it the panel reads as part of the page
     under test rather than as app chrome. */
  box-shadow: 0 8px 24px rgb(0 0 0 / 55%);
}
.select-group + .select-group { border-top: 1px solid var(--line); margin-top: 4px; padding-top: 4px; }
.select-group-label {
  padding: 5px 9px 3px;
  color: var(--text-1);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.select-option {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 9px 5px 4px;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
}
/* Hover and keyboard share one highlight: two different treatments for "the
   row you are about to choose" is one too many. */
.select-option.active { background: var(--chrome-3); }
/* The tick's column is always reserved, so labels do not shift by 12px when
   the selection moves. */
.select-tick { display: flex; width: 14px; justify-content: center; color: var(--text-0); }


/* Segmented groups. Pressed is a fill step and weight, never hue. */
.segmented {
  flex: 0 0 auto;
  display: inline-flex;
  height: 30px;
  /* A `div`, unlike a `button`, does not get the UA's border-box: without
     this the 1px border makes the group 32px and it sits 1px proud of every
     other 30px control in the row. */
  box-sizing: border-box;
  border: 1px solid var(--line);
  border-radius: 5px;
  overflow: hidden;
}
.segmented button {
  height: 100%;
  padding: 0 11px;
  font-size: 11.5px;
  background: var(--chrome-2);
  color: var(--text-1);
  border: 0;
  border-right: 1px solid var(--line);
  border-radius: 0;
  cursor: pointer;
}
.segmented button:last-child { border-right: 0; }
.segmented button[aria-pressed='true'] { background: var(--chrome-3); color: var(--text-0); }


/* Focus is a 1px inset neutral ring, never Chromium's accent outline. */
.chrome :is(button, select, input):focus { outline: none; }
.chrome :is(button, select, input):focus-visible {
  outline: 1px solid var(--text-1);
  outline-offset: -1px;
}
/* The real select is transparent, so its ring must land on the shell. */
.select-shell:has(select:focus-visible) { outline: 1px solid var(--text-1); outline-offset: -1px; }

.panes { flex: 1 1 auto; display: flex; min-height: 0; }
.pane {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--surround);
}
/* The native (or image) pane takes its share of everything the seam leaves;
   the target pane grows into the rest, so the 1px divider comes out of the
   row rather than out of one pane's half and skewing the ratio.

   The `clamp()` is the *stored* ratio's floor, and the reason it lives in CSS
   rather than in the drag: a ratio saved on a wide monitor and restored on a
   narrow window is applied as close as 240px a side allows, without being
   written back — grow the window and the preference returns on its own, with
   no resize plumbing to keep in step. The drag enforces the same floor in JS,
   where it also decides what to persist. */
.app[data-panes='both'] .panes > .pane:first-child {
  flex-grow: 0;
  flex-shrink: 0;
  /* Rounded to whole pixels, and this is load-bearing. `TargetCanvas`
     measures its pane with `clientWidth`, which *rounds*: a 1129.5px pane
     reads as 1130, fit sizes the canvas to 1130, and the render then
     overflows its own pane by half a pixel — which quietly turns off the
     `safe center` centring and leaves the pane scrollable. Whole-pixel panes
     keep the measurement honest. (The old `flex: 1 1 50%` hit the same thing
     on an odd-width window; taking the seam out of the row would have made
     it every window.) */
  flex-basis: round(
    down,
    clamp(
      var(--pane-min),
      calc((100% - var(--seam)) * var(--split)),
      calc(100% - var(--seam) - var(--pane-min))
    ),
    1px
  );
}

/* The seam. A flex item exactly one pixel wide — the hairline the target pane
   used to draw as its own `border-left` — with the grab area straddling it as
   an overflowing pseudo-element, so the handle is 6px to the pointer and 1px
   to the eye. The style spec forbids decorating anything that touches a pane,
   and a resize handle drawn as furniture would be decoration. */
.pane-divider {
  flex: 0 0 var(--seam);
  /* Positioned *and* raised: `.target-wrap` next door is positioned too and
     comes later in the DOM, so without a `z-index` the target pane's canvas
     paints over the half of the grab area that overhangs it and swallows the
     press. */
  position: relative;
  z-index: 1;
  background: var(--line);
  cursor: col-resize;
  /* Or the drag scrolls the pane instead of moving the seam. */
  touch-action: none;
}
.pane-divider::before {
  content: '';
  position: absolute;
  inset: 0 -2.5px;
}
.pane-divider:hover, .pane-divider:focus-visible { background: var(--text-1); }
/* Under the pointer the seam lights up its full height rather than growing a
   handle. Every split-pane tool worth copying does it this way — VS Code,
   Framer, Retool, Rive all leave the divider a hairline and let the cursor and
   the widened hit area carry the affordance. Nothing floats over the pane
   pixels this app exists to show. Painted as an overlay, so no layout moves
   and the target pane is never re-rendered for a mouse-over. */
.pane-divider::after {
  content: '';
  position: absolute;
  inset: 0 -1px;
  background: var(--text-1);
  opacity: 0;
}
.pane-divider:hover::after, .pane-divider:focus-visible::after { opacity: 1; }
/* The hairline is the affordance; a ring around a 1px element would be a box
   floating on the seam. Same reasoning as `.target-canvas:focus`. */
.pane-divider:focus-visible { outline: none; }
/* Square corners, no shadow, no blur: a rounded or blurred pane edge would
   destroy the very corner pixels the user opened this app to inspect. */
.pane-body { flex: 1 1 auto; min-height: 0; overflow: auto; }
/* One pixel short of the seam. The native view is an OS-composited layer sized
   to exactly this rect, so anything the renderer paints inside it is hidden
   underneath — without the inset, the divider's hover band would appear on the
   target side only, since that side is the renderer's own canvas. The view
   still sits exactly over the slot; the slot is simply 1px narrower. */
.native-slot { width: calc(100% - 1px); height: 100%; }
/* The backing store is already at device resolution; this only guards against
   a fractional CSS size introducing a resample. */
.target-canvas { display: block; image-rendering: pixelated; }
/* The canvas itself never carries a ring: an outline over its edge would sit
   on the very pixels under inspection. Keyboard focus in the target pane is
   shown on the pane's own hairlines instead, which touch no page pixel. */
.target-canvas:focus { outline: none; }
/* The seam moved out of `.target-pane`'s border and into its own element, so
   the focus signal reaches it from the row both panes share — `:has` on the
   ancestor, since the divider is the canvas's *earlier* sibling's sibling and
   no combinator walks backwards. The footer rule is unchanged, and carries
   the whole signal on its own in solo target, where there is no seam. */
.panes:has(.target-canvas:focus-visible) .pane-divider { background: var(--text-1); }
.target-pane:has(.target-canvas:focus-visible) .pane-footer { border-top-color: var(--text-1); }

/* Signature: each pane always states what it is showing, so a screenshot of
   Obsrv is self-documenting. */
.pane-footer {
  flex: 0 0 auto;
  display: flex;
  gap: 10px;
  padding: 5px 10px;
  font: 400 11px/1.4 var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-1);
  background: var(--chrome-1);
  border-top: 1px solid var(--line);
  white-space: nowrap;
  overflow: hidden;
}
.pane-footer .role { color: var(--text-0); letter-spacing: 0.08em; }

/* The calibration hint (`DiagonalHint`): its own row above the footer, because
   `.pane-footer` never wraps and would clip a sentence to nothing on a narrow
   pane. Same chrome as the footer, so it reads as the pane talking about
   itself rather than as an alert bar. */
.pane-hint {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  font: 400 11px/1.4 var(--mono);
  color: var(--text-0);
  background: var(--chrome-2);
  border-top: 1px solid var(--line);
}
.pane-hint-text { flex: 1 1 260px; min-width: 0; }
.pane-hint-action {
  flex: 0 0 auto;
  font: inherit;
  color: var(--text-0);
  background: var(--chrome-3);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 2px 8px;
  cursor: pointer;
}
.pane-hint-action:hover { border-color: var(--text-1); }
.pane-hint-error { flex: 1 1 100%; color: var(--error); }

/* The rotate control. Same 30px border-box and the same `--chrome-3` pressed
   fill as `.segmented`, so the screen row keeps one hit-target rhythm and one
   way of marking a choice. Two outlines of the screen you get, rather than the
   words: "Portrait"/"Landscape" would take 150px in a row that already holds
   two selects and three groups, and the shape reads faster than either word.
   The buttons are 28px like `.surround-control`'s, for the same reason — the
   glyph is the affordance and the button is only the target.

   No hue anywhere, pressed included: this control sits a few pixels from the
   pane whose greys the user came to judge. */
.orientation-control {
  box-sizing: border-box;
  flex: 0 0 auto;
  display: inline-flex;
  height: 30px;
  border: 1px solid var(--line);
  border-radius: 5px;
  overflow: hidden;
}
.orientation-control button {
  width: 28px;
  height: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-right: 1px solid var(--line);
  border-radius: 0;
  background: var(--chrome-2);
  color: var(--text-1);
  cursor: pointer;
}
.orientation-control button:last-child { border-right: 0; }
.orientation-control button[aria-pressed='true'] { background: var(--chrome-3); color: var(--text-0); }

/* Segmented surround control: the same 30px border-box as `.segmented` and
   `.select-shell`, so the screen row has one hit-target rhythm. `box-sizing`
   for the same reason `.segmented` needs it — a `div` gets no border-box from
   the UA, so the 1px border would otherwise push the group to 32px.

   The swatch stays 12px: the swatch is the affordance, the button around it is
   just the target. The pressed state stays an inset `--text-0` ring, and is
   the one place the chrome does not use the `--chrome-3` fill step — a fill
   ringing a 12px swatch competes with the swatch for the eye, and `--chrome-3`
   lands two steps from the graphite swatch itself. This is the one control
   whose whole job is judging a tint, so the marker stays off the tint. */
.surround-control {
  box-sizing: border-box;
  flex: 0 0 auto;
  display: inline-flex;
  height: 30px;
  border: 1px solid var(--line);
  border-radius: 5px;
  overflow: hidden;
}
.surround-control button {
  width: 28px;
  height: 100%;
  border: 0;
  border-right: 1px solid var(--line);
  border-radius: 0;
  background: var(--chrome-2);
  cursor: pointer;
}
.surround-control button:last-child { border-right: 0; }
.surround-control button[aria-pressed='true'] { box-shadow: inset 0 0 0 2px var(--text-0); }
.surround-swatch { display: block; width: 12px; height: 12px; margin: 0 auto; }

/* Numbers never jitter as a slider moves. */
.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* Agent control opens a loopback server, so the chip persists while it is
   enabled. Something else driving the app is exactly the "attention, never
   decoration" case the palette reserves --warn for: the user must be able to
   tell at a glance that the window is not only theirs. Standing state is the
   warn hue outlined; an arriving command fills it. No blur — a glow beside the
   panes would spill light onto the pixels being measured, which is the one
   thing this chrome may never do. */
.agent-activity {
  flex: 0 0 auto;
  color: var(--warn);
  border: 1px solid var(--warn);
  border-radius: 4px;
  padding: 2px 7px;
  font-size: 10px;
  letter-spacing: 0.08em;
  white-space: nowrap;
  background: transparent;
  /* `font: inherit` would win over `font-size: 10px` above (it's a shorthand,
     declared later, and resets font-size to inherit too) — undoing the one
     thing this rule is for. Only stop the UA's button font: family and
     weight. Line-height also needs restoring — a <button> gets the UA form-
     control `line-height: normal`, which a <span> never had, and that alone
     changes the chip's height (verified: `normal` renders 18px tall here vs.
     14px/20px total with the inherited 1.4 line-height, matching the old
     span). `text-align` and `margin` differ from a <span>'s too, but this
     button's width always hugs its text, so neither one is visible. */
  font-family: inherit;
  font-weight: inherit;
  line-height: inherit;
  cursor: pointer;
}
.agent-activity.active { color: var(--chrome-0); background: var(--warn); border-color: var(--warn); }
.agent-activity:hover { text-decoration: line-through; }

/* The consent bar (Task 8): a second launch knocked while agent control is
   off, and only the app can ask. Non-modal — it sits in flow under the
   toolbar rather than over the panes, so it never steals focus from the page
   underneath it. */
.consent-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 12px;
  background: var(--warn-bg, rgba(255, 180, 0, 0.12));
  border-bottom: 1px solid var(--warn);
  font-size: 12px;
}
.consent-bar button { font: inherit; padding: 3px 10px; border-radius: 4px; border: 1px solid var(--warn); background: transparent; cursor: pointer; }
.consent-bar .consent-allow { background: var(--warn); color: var(--chrome-0); }

/* Drawers sit beside the panes, never over them: the native WebContentsView is
   an OS-level overlay and would cover anything painted on top of it. */
.body { flex: 1 1 auto; display: flex; min-height: 0; }
.panes { min-width: 0; }
.drawer {
  /* Border-box against `--drawer-w`: 309 - 28 padding - 1 border = the 280px
     of content this has always had, now stated once for both this and the
     URL-history clamp. The content keeps its full width and hangs from the
     right edge, so while the drawer slides it is revealed, not reflowed. */
  box-sizing: border-box;
  flex: 0 0 var(--drawer-w);
  display: flex;
  justify-content: flex-end;
  overflow: hidden;
  background: var(--chrome-1);
  border-left: 1px solid var(--line);
}
.drawer .controls {
  box-sizing: border-box;
  flex: 0 0 308px;
  width: 308px;
  height: 100%;
  overflow-x: hidden;
  overflow-y: auto;
  padding: 12px 14px;
}

/* Dense inspector: ~11px labels, the value pinned right in the mono face. */
.controls h2 {
  margin: 18px 0 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-1);
}
.controls h2:first-child { margin-top: 0; }
/* Sections read as sections: air on both sides of a rule between them, and
   the heading sat on its rule rather than floating between two. */
.panel-section { padding: 18px 0 20px; scroll-margin-top: 8px; border-radius: 4px; }
.panel-section:first-child { padding-top: 0; }
.panel-section:last-child { padding-bottom: 8px; }
.panel-section + .panel-section { border-top: 1px solid var(--line); }
.panel-section h2 { margin-top: 0; position: relative; display: flex; align-items: center; gap: 6px; }
/* The section's explanation, behind an info mark: a bubble on hover or focus.
   The bubble is positioned against the heading, not the mark, so it spans the
   drawer's width under the heading — the drawer clips anything outside its
   box, and a bubble hung from the mark ran under the pane. */
.info {
  all: unset;
  position: static;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  color: var(--text-1);
  cursor: help;
}
.info:hover, .info:focus-visible { color: var(--text-0); }
.info::after {
  content: attr(data-tip);
  display: none;
  position: absolute;
  z-index: 70;
  left: 0;
  right: 0;
  top: calc(100% + 6px);
  padding: 8px 10px;
  box-sizing: border-box;
  font-size: 11px;
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: normal;
  text-transform: none;
  white-space: normal;
  color: var(--text-0);
  background: var(--chrome-3);
  border: 1px solid var(--line);
  border-radius: 5px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}
.info:hover::after, .info:focus-visible::after { display: block; }
/* A footer fact brought this section up: light it for a beat. */
.panel-section.flash { animation: panel-flash 0.9s ease-out; }
@keyframes panel-flash { from { background: var(--chrome-3); } to { background: transparent; } }
/* The menus the drawer took from the toolbar fill its width. */
.drawer .select-shell { display: block; }
.drawer .select-trigger { width: 100%; box-sizing: border-box; }
/* A readout that is also the way to its control. */
.fact-link { all: unset; cursor: pointer; text-decoration: underline dotted; text-underline-offset: 2px; }
.fact-link:hover, .fact-link:focus-visible { color: var(--text-0); }
.controls p { margin: 6px 0; font-size: 11px; }
.control { display: block; margin: 10px 0; }
.control-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 4px;
  font-size: 11px;
  color: var(--text-1);
}
.control-row .num { color: var(--text-0); }
.control.inline { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-1); }
.control.inline > span { margin: 0; }
/* accent-color pins the thumb and filled track to a neutral; unset, Chromium
   paints them in the OS accent — the one hue this chrome may never carry. */
.control input[type='range'] { display: block; width: 100%; margin: 0; accent-color: var(--text-0); }
.control input[type='checkbox'] { margin: 0; accent-color: var(--text-0); }
.control input[type='number'], .control select {
  width: 100%;
  height: 24px;
  box-sizing: border-box;
  padding: 0 6px;
  background: var(--field);
  color: var(--text-0);
  border: 1px solid var(--line);
  border-radius: 4px;
}
/* A numeric field reads like the readouts: digits flush right. */
.control input.num { text-align: right; }
.readout {
  font: 400 11px/1.6 var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-1);
}
.field-error { color: var(--error); }

/* Same 1px inset neutral ring as the toolbar; a range input's ring sits just
   outside its track instead, where an inset one would cross the thumb. */
.drawer :is(button, select, input):focus { outline: none; }
.drawer :is(button, select, input):focus-visible {
  outline: 1px solid var(--text-1);
  outline-offset: -1px;
}
.drawer input[type='range']:focus-visible { outline-offset: 2px; }

.fatal { padding: 48px; max-width: 44em; }
.fatal h1 { font-size: 20px; margin: 0 0 12px; }

/* Image mode. In normal flow, above the panes: an overlay here would sit
   behind the native WebContentsView. */
.strip {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  background: var(--chrome-1);
  border-bottom: 1px solid var(--line);
}
.strip button {
  background: var(--chrome-2);
  color: var(--text-0);
  border: 1px solid var(--line);
  border-radius: 4px;
  height: 24px;
  padding: 0 10px;
  cursor: pointer;
}
.strip button:focus { outline: none; }
.strip button:focus-visible { outline: 1px solid var(--text-1); outline-offset: 1px; }
/* `safe`: a file wider than the pane scrolls from its left edge instead of
   losing it off-screen. */
.image-pane .pane-body { display: flex; align-items: flex-start; justify-content: safe center; }
.image-pane img { display: block; image-rendering: pixelated; }

/* Pinned to the right so they land over the target pane, never over the native
   view on the left. The drag hint is fixed rather than a strip: a strip would
   shift the panes under the pointer and flicker dragleave/dragenter. */
.toast,
.drop-hint {
  position: fixed;
  right: 16px;
  bottom: 16px;
  /* Above the stall notice (2) and the agent highlight (1): these are the
     app talking to the user and must not sit under an overlay in the pane. */
  z-index: 3;
  padding: 8px 12px;
  background: var(--chrome-2);
  color: var(--text-0);
  border: 1px solid var(--line);
  border-radius: 6px;
  pointer-events: none;
}
.strip button[aria-pressed='true'] { border-color: var(--text-1); font-weight: 600; }

/* Spec §9 stall notice. Inside the target pane's scroll box, never over the
   native view on the left. Sticky rather than absolute so it stays visible
   while the pane is scrolled; a direct block child of `.pane-body` (a sibling
   before `.target-wrap`, not inside it), so it is the pane's width and its
   `left: 0` holds while a wide canvas is scrolled horizontally. The fill is a
   neutral grey: only the rule under it is red, so the one chromatic thing on
   screen is the error itself. */
/* At 1:1 the render usually overflows and the pane is a scroll box, so it
   starts at the top-left and you pan around it. When it is *smaller* than the
   pane — a small preset, fit mode, or solo target on a wide window — hugging
   the corner leaves it stranded against the chrome with dead field to the
   right. Centre it instead.

   `safe` is what lets one rule serve both cases: it falls back to start
   alignment the moment the content overflows, so the top-left of an oversized
   render stays reachable by scrolling. Plain `center` would push it out of
   reach. The image pane already centres this way. */
.target-pane .pane-body {
  display: flex;
  flex-direction: column;
  align-items: safe center;
  justify-content: safe center;
}
/* The stall notice spans the pane, not the canvas: it is sticky at `left: 0`
   and must stay pane-width while a wide canvas scrolls under it, so it opts
   out of the centring its sibling wants. */
.target-pane .pane-body > .stall { align-self: stretch; }

/* position: relative anchors the agent highlight to the canvas origin. */
.target-wrap { min-width: min-content; position: relative; }

/* Agent-control highlight: a temporary neutral marker over the target-pixel
   rect an agent is pointing at. Style-spec law: no hue — a light outline with
   a dark dashed inner edge reads on any page without tinting it — and no
   pointer interception over the pixels under inspection. */
.agent-highlight {
  position: absolute;
  z-index: 1;
  pointer-events: none;
  outline: 2px solid var(--text-0);
  border: 1px dashed var(--chrome-0);
  box-sizing: border-box;
}
/* The inspector's highlight: the agent highlight's twin, for the element
   under the pointer. Same neutral ring, same no-interception. */
.inspect-highlight {
  position: absolute;
  z-index: 1;
  pointer-events: none;
  outline: 2px solid var(--text-0);
  border: 1px dashed var(--chrome-0);
  box-sizing: border-box;
}
/* The inspector's switch lives in the target footer. It must not make the
   strip taller than the native footer's, or the panes' bottoms drift apart:
   sized inside the 11px/1.4 line box, never outside it. */
.pane-footer { align-items: center; }
.pane-footer .trailing { margin-left: auto; display: flex; align-items: center; }
.inspect-toggle {
  font: inherit;
  font-size: 10px;
  line-height: 13px;
  height: 15px;
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: transparent;
  color: var(--text-1);
  cursor: pointer;
}
.inspect-toggle[aria-pressed='true'] { background: var(--chrome-3); color: var(--text-0); }
.inspect-toggle:focus { outline: none; }
.inspect-toggle:focus-visible { outline: 1px solid var(--text-1); outline-offset: 1px; }
.stall {
  position: sticky;
  top: 0;
  left: 0;
  /* Above the agent highlight (z-index 1): an error notice outranks a
     demo marker. */
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: var(--chrome-1);
  border-bottom: 1px solid var(--error);
  color: var(--text-0);
}
.stall button {
  background: var(--chrome-2);
  color: var(--text-0);
  border: 1px solid var(--line);
  border-radius: 4px;
  height: 22px;
  padding: 0 10px;
  cursor: pointer;
}
.stall button:focus { outline: none; }
.stall button:focus-visible { outline: 1px solid var(--text-1); outline-offset: 1px; }

/* v1.1 fit & pan. The 1:1/Fit control is a `Segmented`, so `.segmented` is
   the whole of its styling — the old `.view-control button` rules lived here,
   and being (0,1,1) and later in the file they beat `.segmented button` and
   split the two groups apart. Nothing about 1:1/Fit is control-specific. */

/* Option is what a view gesture needs, so Option is what the cursor answers:
   the jump-to-1:1 zoom in fit, the grab affordances for the pan chords
   (middle drag, Option+drag) at 1:1. Unmodified, both views are interactive
   and the page's own cursors show through. Cursors only — no overlay may sit
   on the pixels under inspection. */
.target-canvas.fit { cursor: zoom-in; }
.target-canvas.pan-ready { cursor: grab; }
.target-canvas.panning { cursor: grabbing; }
.target-canvas.inspecting { cursor: crosshair; }

/* Notice shown when the renderer loads outside Electron (plain browser tab). */
.browser-notice {
  max-width: 32rem;
  margin: 20vh auto 0;
  padding: 1.5rem;
  background: var(--chrome-1);
  border: 1px solid var(--chrome-2);
  color: var(--text-0);
  line-height: 1.5;
}
.browser-notice p { margin: 0.75rem 0 0; color: var(--text-1); }
.browser-notice code { font-family: var(--mono); }

.version-block { margin: 8px 0; }
.version-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 3px 0;
  color: var(--text-1);
}
.version-row .version-current,
.version-row .version-latest,
.version-row .version-checked {
  color: var(--text-0);
  text-align: right;
}
.link {
  background: none;
  border: 0;
  padding: 0;
  color: var(--text-0);
  text-decoration: underline;
  cursor: pointer;
  font: inherit;
}
.check-now,
.clear-history {
  width: 100%;
  height: 24px;
  margin-top: 8px;
  background: var(--chrome-2);
  color: var(--text-0);
  border: 1px solid var(--line);
  border-radius: 4px;
  cursor: pointer;
}
/* Nothing to erase: the button says so rather than pretending to act. */
.clear-history:disabled { color: var(--text-1); cursor: default; }

/* --- empty state ---------------------------------------------------------- */
/* Shown when a tab has no page. It covers the target canvas rather than
   replacing it: the canvas keeps its GL context and frame subscription, so the
   first navigation does not pay for a context restore, and the native slot
   keeps reporting its bounds. `--surround` is the pane field, so the state sits
   on the same ground the render will. */
.empty-state {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  padding: 24px;
  background: var(--surround);
  /* Above `.pane-divider`, which is itself raised to 1: the seam would
     otherwise draw its hairline straight through the illustration. Covering it
     must not disarm it, though — the split is still adjustable on a blank tab —
     so everything here is transparent to the pointer except the form. */
  z-index: 2;
  pointer-events: none;
}
.empty-form { pointer-events: auto; }
.panes { position: relative; }
.empty-state svg { width: 244px; max-width: 100%; height: auto; }
.empty-lede {
  margin: 0;
  color: var(--text-1);
  text-align: center;
  max-width: 34ch;
}
.empty-form { display: flex; gap: 8px; width: 100%; max-width: 420px; }
.empty-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 30px;
  padding: 0 10px;
  color: var(--text-0);
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: 4px;
  font: inherit;
  font-family: var(--mono);
}
.empty-input:focus-visible { outline: none; border-color: var(--text-1); }
.empty-go {
  height: 30px;
  padding: 0 14px;
  color: var(--text-0);
  background: var(--chrome-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  font: inherit;
}
.empty-go:hover:not(:disabled) { background: var(--chrome-3); }
.empty-go:disabled { color: var(--text-1); opacity: 0.6; }
.empty-error { margin: 0; color: var(--error); }

/* The load-error state. The empty state's geometry and its column — absolute
   over both panes, above the divider, transparent to the pointer except its
   own control — because it is the same kind of thing: the tab has nothing to
   show and one way out. Drawn in the surround rather than on a card: a
   bordered panel over an empty window read as a dialog. */
.load-error-state {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  padding: 24px;
  background: var(--surround);
  text-align: center;
  z-index: 2;
  pointer-events: none;
}
.load-error-state svg { width: 244px; max-width: 100%; height: auto; }
.load-error-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  max-width: 46ch;
}
.load-error-lede { margin: 0; color: var(--text-0); font-weight: 600; }
.load-error-url {
  margin: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-1);
  font-family: var(--mono);
  font-size: 12px;
}
.load-error-why { margin: 0; color: var(--text-1); }
.load-error-code { font-family: var(--mono); font-size: 12px; opacity: 0.75; }
.load-error-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.load-error-retry {
  pointer-events: auto;
  height: 30px;
  padding: 0 14px;
  color: var(--text-0);
  background: var(--chrome-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  font: inherit;
}
.load-error-retry:hover { background: var(--chrome-3); }
.load-error-hint { margin: 0; color: var(--text-1); font-size: 12px; }
/* The readouts stay legible through the empty state: they still name the screen
   being simulated, which is the one fact that survives having no page. Stacking
   rather than insetting the overlay, so nothing depends on the footer's height. */
.pane-footer { position: relative; z-index: 3; }

/* --- overlay view --------------------------------------------------------- */
/* The menu view is a transparent sheet over the whole window, so its page must
   paint nothing of its own — a background here would hide both panes. */
:root[data-overlay] html,
:root[data-overlay],
:root[data-overlay] body,
:root[data-overlay] #root {
  background: transparent;
}
/* Spans the overlay so a press anywhere outside the menu dismisses it, which is
   what a native menu's invisible tracking window does. */
.menu-backdrop { position: fixed; inset: 0; }

/* A picker's host in the overlay: an input laid over the target's own on the
   canvas, kept invisible — the page already draws the field, and only
   Chromium's picker, which hangs on this one, should show. Opacity rather than
   visibility, or it could take neither the click nor the focus. */
.picker-host {
  position: fixed;
  z-index: 60;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
}

/* --- settings modal ------------------------------------------------------- */
/* Covers the panes deliberately: the native view is taken off screen while this
   is up (see `setNativeObscured`), which is correct here and was not for the
   transient menus that used to do the same thing. */
.modal-backdrop {
  position: absolute;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  background: rgb(0 0 0 / 55%);
}
.settings-modal {
  display: grid;
  grid-template-columns: 196px 1fr;
  width: min(760px, calc(100% - 48px));
  height: min(520px, calc(100% - 48px));
  background: var(--chrome-1);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 18px 48px rgb(0 0 0 / 60%);
  overflow: hidden;
}
.settings-modal:focus { outline: none; }

.settings-nav {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 10px 8px;
  overflow: auto;
  background: var(--chrome-0);
  border-right: 1px solid var(--line);
}
.settings-nav button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 7px 9px;
  font: inherit;
  font-size: 12px;
  text-align: left;
  color: var(--text-1);
  background: none;
  border: 0;
  border-radius: 5px;
  cursor: pointer;
}
.settings-nav button:hover { background: var(--chrome-1); }
/* The same fill step every other pressed control in this app uses — never hue. */
.settings-nav button.on { background: var(--chrome-3); color: var(--text-0); }
/* Agent control is the exception the style spec already carves out: an open
   control server is attention, not decoration. */
.nav-chip {
  padding: 2px 4px;
  font: 400 10px/1 var(--mono);
  color: var(--warn);
  border: 1px solid var(--warn);
  border-radius: 3px;
}

.settings-body { display: flex; flex-direction: column; min-width: 0; }
.settings-body .controls { flex: 1 1 auto; overflow: auto; padding: 16px 20px; }
.settings-foot {
  flex: 0 0 auto;
  display: flex;
  justify-content: flex-end;
  padding: 12px 20px;
  border-top: 1px solid var(--line);
}
/* No Cancel beside it: settings commit as they change (main can refuse one, and
   the panel rolls that field back), so a Cancel would promise an undo that does
   not exist. */
.settings-done {
  height: 28px;
  padding: 0 14px;
  font: inherit;
  font-size: 12px;
  color: var(--text-0);
  background: var(--chrome-2);
  border: 1px solid var(--line);
  border-radius: 5px;
  cursor: pointer;
}
.settings-done:hover { background: var(--chrome-3); }
.settings-modal :is(button, input, select):focus { outline: none; }
.settings-modal :is(button, input, select):focus-visible {
  outline: 1px solid var(--text-1);
  outline-offset: 1px;
}

/* The viewer control, below the panel's own. Buttons rather than a select: five
   short labels fit, and the choice is worth seeing all of at once. */
.vision-control {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 8px 0 4px;
}
.vision-control button {
  flex: 0 0 auto;
  height: 24px;
  padding: 0 8px;
  font: inherit;
  font-size: 11px;
  color: var(--text-1);
  background: var(--chrome-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  cursor: pointer;
}
.vision-control button:hover { background: var(--chrome-3); }
/* The same pressed idiom as every other group in this app: a fill step and
   weight, never hue — which matters more here than anywhere, since the control
   next to it is busy changing the colours. */
.vision-control button[aria-pressed='true'] { background: var(--chrome-3); color: var(--text-0); }
