:root {
  --bg: #0e1116;
  --bg-elevated: #161b23;
  --bg-inset: #0a0d12;
  --border: #262d38;
  --text: #e6e9ef;
  --text-muted: #8b95a5;
  --accent: #4c8dff;
  --accent-strong: #2f6fe4;
  --ok: #3fbf7f;
  --warn: #e5a13a;
  --error: #f0616d;
  --radius: 10px;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

* {
  box-sizing: border-box;
}

/* `hidden` only removes an element in the UA stylesheet's `display: none`, and
   ANY author `display` declaration beats that regardless of specificity. Both
   top-level views set their own: `.centered` is a grid, `.workspace` is a flex
   column. So `hidden` did nothing on them, and because each is `flex: 1 1 auto`
   they split the page: the login card stayed on screen after signing in and the
   viewport was squeezed into the lower half of the window.

   Per-element `[hidden]` overrides existed for `#viewport-canvas`, `.placeholder`
   and `.overlay`, which is exactly how the two views came to be forgotten. One
   authoritative rule is harder to get wrong than remembering to add a fourth. */
[hidden] {
  display: none !important;
}

html,
body {
  height: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  display: flex;
  flex-direction: column;
}

/* ------------------------------------------------------------------ brand */

.brand {
  font-weight: 650;
  letter-spacing: 0.2px;
}

.panel-head-right {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.user {
  color: var(--text-muted);
  font-size: 13px;
}

.badge {
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.badge-demo {
  background: rgba(229, 161, 58, 0.15);
  border: 1px solid rgba(229, 161, 58, 0.45);
  color: var(--warn);
}

/* ------------------------------------------------------------------ layout */

main {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.centered {
  flex: 1 1 auto;
  display: grid;
  place-items: center;
  padding: 24px;
}

.card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px;
}

.login-card {
  width: min(380px, 100%);
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.login-card h1 {
  margin: 0;
  font-size: 20px;
}

/* The stage is the whole tab and the panel floats over it, so neither the grid
   column nor the padding that used to surround the viewport exists any more. */
.workspace {
  flex: 1 1 auto;
  min-height: 0;
  position: relative;
  overflow: hidden;
}

/* ------------------------------------------------- landing page and pricing */

/* The landing and pricing pages scroll inside the view rather than the body, so the
   page behaves the same whether or not it is the only thing on screen -- which it is,
   since exactly one view is ever shown. `min-height: 0` is what actually lets a flex
   child scroll: without it the child is at least its content size and the scroll never
   happens, which is a particularly confusing way for a page to appear cut off. */
.marketing {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 40px 24px 64px;
}

.marketing-inner {
  width: min(980px, 100%);
  margin: 0 auto;
}

.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
  padding: 20px 0 40px;
}

/* For a page with no headline copy above it -- pricing. The generous top padding above
   exists to give the landing hero room to breathe; on a page that opens with a heading
   it is just a gap. */
.hero-compact {
  padding: 8px 0 24px;
}

.hero h1 {
  margin: 0;
  /* Floors at 24px so the headline does not collapse on a phone, and ceilings at 38px so
     it does not run to four lines on a wide monitor. */
  font-size: clamp(24px, 3.6vw, 38px);
  line-height: 1.2;
  letter-spacing: -0.4px;
}

.hero-sub {
  margin: 0;
  max-width: 62ch;
  font-size: 15px;
}

.hero-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 6px;
}

.hero-note {
  margin: 4px 0 0;
  max-width: 58ch;
  font-size: 12.5px;
}

.feature-grid {
  display: grid;
  /* auto-fit rather than a fixed column count: the same markup then lays out as two
     columns on a laptop and one on a phone, with no media query to keep in step. */
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 14px;
}

.feature {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 18px;
}

.feature h3 {
  margin: 0 0 6px;
  font-size: 14px;
}

.feature p {
  margin: 0;
  font-size: 13px;
}

.plan-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px;
  align-items: start;
}

.plan {
  display: flex;
  flex-direction: column;
  gap: 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
}

/* The recommended tier, marked by border rather than by a filled background: a filled
   card reads as "selected", which is wrong for something the reader has not chosen yet. */
.plan.is-highlight {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);
}

.plan-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}

.plan-name {
  margin: 0;
  font-size: 16px;
}

.plan-badge {
  padding: 2px 8px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: 999px;
  color: var(--accent);
  font-size: 10px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  vertical-align: middle;
}

.plan-price {
  margin: 0;
  font-size: 20px;
  font-weight: 650;
  white-space: nowrap;
}

.plan-tagline {
  margin: 0;
  font-size: 13px;
}

.plan-features {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
  padding-left: 18px;
  font-size: 13px;
}

.plan-choose {
  width: 100%;
}

.pricing-note {
  margin: 14px 0 0;
  font-size: 12.5px;
  text-align: center;
}

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

.panel-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* The panel is narrow, so the address bar stacks: the field takes a row of its
   own and the four buttons share the row below, instead of five controls
   competing for one line. */
.urlbar {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 12px;
}

.urlbar-actions {
  display: flex;
  gap: 6px;
}

.urlbar-actions .btn-icon {
  flex: 1 1 0;
}

.urlbar input {
  width: 100%;
  min-width: 0;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: 12.5px;
}

.urlbar input:disabled {
  opacity: 0.55;
}

.urlbar input:focus {
  outline: none;
  border-color: var(--accent);
}

.pill {
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  background: rgba(139, 149, 165, 0.15);
  color: var(--text-muted);
  border: 1px solid var(--border);
}

.pill[data-status='starting'],
.pill[data-status='stopping'] {
  background: rgba(229, 161, 58, 0.15);
  border-color: rgba(229, 161, 58, 0.45);
  color: var(--warn);
}

.pill[data-status='running'] {
  background: rgba(63, 191, 127, 0.15);
  border-color: rgba(63, 191, 127, 0.45);
  color: var(--ok);
}

.pill[data-status='failed'] {
  background: rgba(240, 97, 109, 0.15);
  border-color: rgba(240, 97, 109, 0.45);
  color: var(--error);
}

/* ----------------------------------------------------------------- buttons */

.btn {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--bg-elevated);
  color: var(--text);
  border-radius: 8px;
  padding: 8px 14px;
  font: inherit;
  font-weight: 550;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease, opacity 120ms ease;
}

.btn:hover:not(:disabled) {
  border-color: #39424f;
  background: #1c222c;
}

.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.btn-primary {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
  color: #fff;
}

.btn-primary:hover:not(:disabled) {
  background: var(--accent);
  border-color: var(--accent);
}

.btn-ghost {
  background: transparent;
}

.btn-icon {
  padding: 8px 10px;
  min-width: 38px;
  font-size: 14px;
  line-height: 1;
}

/* ---------------------------------------------------------------- viewport */

/* The stream gets the whole tab. The surfaces stay sized to the stream's own
   aspect ratio and are centred here rather than stretched to fill, because
   each element's box has to equal the picture it shows -- the input mapping
   scales pointer positions by that box (see `#toRemote`), so an element larger
   than its picture would send every click to the wrong place. */
.viewport {
  position: absolute;
  inset: 0;
  /* Above the Opera Zero chrome canvas, which is the same size and sits beneath it. Until the
     page positions this box onto the shell's page rectangle it fills the workspace, so the app
     looks the same as it always did if the chrome never mounts. */
  z-index: 1;
  background: #000;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ---------------------------------------------------- the Opera Zero chrome */

/* The chrome is a canvas the WASM shell paints: rail, tab strip and address bar, laid out by the
   same scene-graph the desktop app uses. It is the whole workspace, with the viewport above it, so
   the page area it leaves unpainted is exactly where the live video shows through.

   It must not swallow presses meant for the page: those land on the viewport (z-index 1), which
   covers the page rectangle. A press outside that rectangle reaches this canvas and is classified
   by the shell. Sizing is done in script -- the canvas' backing store is its CSS box times the
   device pixel ratio -- so nothing here sets `width`/`height`. */
.oz-chrome {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
}

/* One sizing rule for both viewport surfaces: the WebRTC <video> humans watch and
   the <canvas> used when a machine is driving the browser over CDP. They are never
   shown at once, but they occupy the same box, so they must be sized identically or
   switching between them would jump.

   Both fill the stage outright and `object-fit` fits the picture inside that box, so
   the letterbox is the element's own background rather than a gap the surrounding
   layout has to leave. That is the largest the picture can be without cropping or
   distorting it.

   The element is therefore NOT the picture -- it is bigger in one axis. Input is
   mapped against the painted area rather than this box, which is what
   `containedRect` is for; mapping against the box offsets every click by half the
   bar. Enlarging the element does not cost any stream quality, because the encoder
   works from the remote resolution and only the display scale changes. */
.viewport-surface {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  outline: none;
  touch-action: none;
  background: #000;
}

/* Stands in for "you can watch but not type": until the server grants control,
   input is discarded, so this has to be visible rather than silent. */
.control-btn {
  position: absolute;
  top: 0.75rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  box-shadow: 0 2px 10px rgb(0 0 0 / 45%);
}

.placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  text-align: center;
  color: var(--text-muted);
  padding: 24px;
}

.overlay {
  position: absolute;
  left: 12px;
  bottom: 12px;
  padding: 5px 10px;
  border-radius: 999px;
  background: rgba(10, 13, 18, 0.85);
  border: 1px solid var(--border);
  font-size: 12px;
  color: var(--text-muted);
}

/* ------------------------------------------------------------------- panel */

/* Floats over the right of the stage rather than taking a column, so the stream
   is never squeezed. `visibility` is transitioned alongside the slide so the
   panel is genuinely gone to the tab order and the accessibility tree while
   closed, without losing the animation to `display: none`. */
.panel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(360px, 100%);
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 0;
  overflow-y: auto;
  padding: 14px;
  background: rgba(14, 17, 22, 0.96);
  backdrop-filter: blur(8px);
  border-left: 1px solid var(--border);
  box-shadow: -18px 0 40px rgb(0 0 0 / 45%);
  transform: translateX(102%);
  visibility: hidden;
  transition: transform 180ms ease, visibility 0s linear 180ms;
}

.panel.is-open {
  transform: none;
  visibility: visible;
  transition: transform 180ms ease, visibility 0s linear 0s;
}

.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  /* Keeps the header clear of the toggle, which floats in this corner in both
     states rather than moving with the panel. */
  padding-right: 46px;
}

.panel-toggle {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 6;
  width: 40px;
  height: 40px;
  padding: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: rgba(22, 27, 35, 0.9);
  backdrop-filter: blur(8px);
  color: var(--text);
  font: inherit;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 2px 12px rgb(0 0 0 / 45%);
}

.panel-toggle:hover {
  border-color: #39424f;
  background: #1c222c;
}

.panel-block {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
}

.panel-block h2 {
  margin: 0 0 10px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  color: var(--text-muted);
}

.kv {
  display: grid;
  grid-template-columns: 108px minmax(0, 1fr);
  gap: 4px 10px;
  margin: 0;
  font-size: 12.5px;
}

.kv dt {
  color: var(--text-muted);
}

.kv dd {
  margin: 0;
  word-break: break-word;
}

.mono {
  font-family: var(--mono);
  font-size: 11.5px;
}

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

/* ------------------------------------------------------------------ inputs */

.field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 12.5px;
  color: var(--text-muted);
}

.field input,
.field select {
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  padding: 9px 10px;
  font: inherit;
}

.field input:focus,
.field select:focus {
  outline: none;
  border-color: var(--accent);
}

.panel-block .field {
  margin-top: 12px;
}

.error {
  margin: 0;
  color: var(--error);
  font-size: 13px;
}

.muted {
  color: var(--text-muted);
}

/* ------------------------------------------------------------------ toasts */

.toasts {
  position: fixed;
  right: 16px;
  bottom: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 50;
  max-width: min(380px, calc(100vw - 32px));
}

.toast {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 13px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  animation: toast-in 140ms ease-out;
}

.toast[data-kind='error'] {
  border-left-color: var(--error);
}

.toast[data-kind='success'] {
  border-left-color: var(--ok);
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* --------------------------------------------------- capture and downloads */

/* The two controls that float next to the panel toggle while the panel is shut.
   They deliberately mirror `.panel-toggle` so the corner reads as one cluster,
   and they hide themselves when they have nothing to offer -- a button that is
   present but useless is worse than no button, because it costs attention. */
.fab {
  position: absolute;
  top: 14px;
  /* Under the toggle (z-index 6), which must stay clickable above everything. */
  z-index: 5;
  height: 40px;
  min-width: 40px;
  padding: 0 12px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(22, 27, 35, 0.9);
  backdrop-filter: blur(8px);
  color: var(--text);
  font: inherit;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 2px 12px rgb(0 0 0 / 45%);
}

.fab:hover {
  border-color: #39424f;
  background: #1c222c;
}

.fab[hidden] {
  display: none;
}

/* Laid out to the left of the panel toggle rather than tucked under it. */
#capture-fab {
  right: 64px;
}

#downloads-fab {
  right: 114px;
}

.fab-time {
  font-variant-numeric: tabular-nums;
  font-size: 12.5px;
  color: var(--text-muted);
}

/* RED means there is something to record. The control is always present while a
   session runs, so colour is what carries the answer to "is a video available?" --
   waiting to be read from a tooltip is not an answer. */
.fab.is-ready {
  border-color: rgba(240, 97, 109, 0.8);
  background: rgba(240, 97, 109, 0.22);
  color: #fff;
}

/* Recording: the same red, and actually blinking, because the elapsed time alone
   is easy to miss while watching the stream. The pulse was a faint glow before,
   which is not a blink -- this drops the opacity, so it is unmistakable. */
.fab.is-recording {
  border-color: rgba(240, 97, 109, 0.95);
  background: rgba(240, 97, 109, 0.38);
  color: #fff;
  animation: fab-blink 1.2s steps(1, end) infinite;
}

/* A fallback download: busy, and amber rather than red, because it is not a recording.
   It blinks for the same reason a recording does -- it is an operation with a
   beginning and an end that the viewer has to be able to see is still running. */
.fab.is-downloading {
  border-color: rgba(229, 161, 58, 0.9);
  background: rgba(229, 161, 58, 0.22);
  color: #fff;
  animation: fab-blink 1.2s steps(1, end) infinite;
}

/* There is a session but nothing worth recording yet: dimmed and neutral, which
   reads as "not now" rather than as a broken control. */
.fab.is-idle {
  opacity: 0.5;
  cursor: default;
}

.fab.is-idle:hover {
  border-color: var(--border);
  background: rgba(22, 27, 35, 0.9);
}

/* Half the cycle at full opacity, half dimmed: a square wave rather than a fade,
   which is what makes it read as blinking instead of breathing. */
@keyframes fab-blink {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.3;
  }
}

.fab-count {
  min-width: 17px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--accent-strong);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  line-height: 17px;
  text-align: center;
}

.btn-record {
  border-color: rgba(240, 97, 109, 0.5);
}

/* Same convention as the floating button: red means a video is ready to record. */
.btn-record.is-ready {
  background: rgba(240, 97, 109, 0.16);
  border-color: rgba(240, 97, 109, 0.8);
  color: #fff;
}

.btn-record.is-recording {
  background: rgba(240, 97, 109, 0.3);
  border-color: rgba(240, 97, 109, 0.9);
  color: #fff;
}

/* Matching the floating button: a download is amber, not red. */
.btn-record.is-downloading {
  background: rgba(229, 161, 58, 0.18);
  border-color: rgba(229, 161, 58, 0.85);
  color: #fff;
}

.badge-recording {
  background: rgba(240, 97, 109, 0.16);
  border: 1px solid rgba(240, 97, 109, 0.45);
  color: var(--error);
}

/* The word "red" in the explanatory copy really is red, so it points at the colour
   it is describing. */
.rec {
  color: var(--error);
  font-weight: 650;
}

.small {
  margin: 10px 0 0;
  font-size: 11.5px;
  line-height: 1.5;
}

/* The remote downloads list. It scrolls inside the panel because a few
   recordings are taller than the panel itself. */
.file-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 260px;
  overflow-y: auto;
}

.file-row {
  display: grid;
  /* name | size | download | delete */
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  align-items: center;
  gap: 8px;
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-inset);
  font-size: 12.5px;
}

.file-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-meta {
  color: var(--text-muted);
  font-size: 11.5px;
  white-space: nowrap;
}

/* The delete button between its first and second click. It has to look clearly
   different from the idle state, otherwise an armed button is just a normal one
   that deletes on the next click. */
.file-row .is-armed {
  border-color: var(--warn);
  color: var(--warn);
}

/* ---------------------------------------------------------------- tabstrip */

/* The aggregated tab strip on the left edge.

   Thin until it is wanted, so the stream keeps the whole tab until the pointer
   actually goes looking for tabs. The expand is DELAYED and the collapse is not:
   without the delay, dragging the pointer across the left edge on the way to
   something else opens the strip and pulls the target out from under the cursor,
   and with an immediate collapse it gets out of the way as soon as the pointer
   leaves. `:focus-within` and the pin both expand at once, because a keyboard user
   tabbing into the search box should not have to wait. */
.tabstrip {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 14px;
  z-index: 6;
  display: flex;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(8px);
  border-right: 1px solid var(--border);
  overflow: hidden;
  transition: width 130ms ease;
}

.tabstrip:hover {
  width: min(300px, 74vw);
  transition-delay: 190ms;
}

.tabstrip:focus-within,
.tabstrip.is-pinned {
  width: min(300px, 74vw);
  transition-delay: 0ms;
}

/* The collapsed rail. Also the click target that pins the strip open, which is the
   only way in on a touch device. */
.tabstrip-rail {
  flex: 0 0 14px;
  width: 14px;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 10px 0 0;
  font: inherit;
}

.tabstrip-rail:hover {
  color: var(--text);
}

.tabstrip-rail-label {
  writing-mode: vertical-rl;
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.75;
}

.tabstrip-rail-count {
  font-size: 10px;
  color: var(--accent);
}

.tabstrip-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  opacity: 0;
  /* Collapsed, the body is transparent but still laid out; without this it would
     swallow clicks meant for the page underneath. */
  pointer-events: none;
  transition: opacity 90ms ease;
}

.tabstrip:hover .tabstrip-body,
.tabstrip:focus-within .tabstrip-body,
.tabstrip.is-pinned .tabstrip-body {
  opacity: 1;
  pointer-events: auto;
}

/* Closed by the viewer, with the close button or Escape.

   This has to defeat hover and focus, not just sit alongside them. The strip is opened by
   `:hover`, so without a rule that outranks it a close would appear to do nothing whenever
   the pointer happened to be over the strip -- and would work whenever it did not, which is
   precisely a panel that "sometimes auto-hides and sometimes doesn't".

   `is-dismissed` alone (with no :hover) is listed so it also outranks `is-pinned`, since both
   have the same specificity and this rule comes later. The two are never set together -- the
   dismiss clears the pin -- but relying on that would make the stylesheet depend on a detail
   of the script. Cleared when the pointer leaves, so the next hover opens the strip again. */
.tabstrip.is-dismissed,
.tabstrip.is-dismissed:hover,
.tabstrip.is-dismissed:focus-within {
  width: 14px;
  transition-delay: 0ms;
}

.tabstrip.is-dismissed .tabstrip-body {
  opacity: 0;
  pointer-events: none;
}

.tabstrip-head {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 8px;
  border-bottom: 1px solid var(--border);
}

.tabstrip-search {
  flex: 1 1 auto;
  min-width: 0;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--bg-inset);
  color: var(--text);
  font: inherit;
  font-size: 12.5px;
}

.tabstrip-search:focus {
  outline: none;
  border-color: var(--accent);
}

/* The filter icons, in a bar directly above the list they narrow.

   Chips rather than a segmented control because the filters COMBINE: each one switches its own
   section on or off and leaves the others alone, so they are independent switches rather than a
   choice of one view. All of them start lit, because all three sections start listed -- the lit
   state has to describe what is actually on screen, or the icons contradict the list under them. */
.tabstrip-filters {
  flex: 0 0 auto;
  display: flex;
  gap: 4px;
  padding: 0 8px 8px;
  flex-wrap: wrap;
}

/* Icon-only now, so these are compact square-ish targets with the count beside the glyph.

   ON is a SOLID accent fill and OFF is a hairline border with a muted glyph. That gap is much wider
   than it used to be, deliberately. The old ON state was a 14% accent tint drawn over the same
   muted grey the OFF state uses, so a row of switched-on filters was indistinguishable from a row
   of disabled ones -- which is precisely how they were reported ("look disabled initially, but in
   fact they are enabled"). A solid fill cannot be misread either way. */
.tabstrip-filter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-width: 32px;
  padding: 4px 7px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 10.5px;
  cursor: pointer;
  white-space: nowrap;
}

.tabstrip-filter:hover {
  color: var(--text);
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
}

.tabstrip-filter.is-on {
  border-color: var(--accent);
  background: var(--accent-strong);
  color: #fff;
}

.tabstrip-filter-glyph {
  font-size: 12px;
  line-height: 1;
}

/* The count. Dimmer than the label so the word still reads first, and hidden entirely when there
   is nothing to count -- an empty filter showing "0" invites a click that does nothing. */
.tabstrip-filter-count {
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
}

.tabstrip-filter-count:empty {
  display: none;
}

.tabstrip-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/* The keyboard hints. They are the only way to open or remove anything, so they are
   written down rather than left to be discovered. */
.tabstrip-foot {
  flex: 0 0 auto;
  display: flex;
  gap: 10px;
  padding: 7px 10px;
  border-top: 1px solid var(--border);
  font-size: 10.5px;
  color: var(--text-muted);
}

.tabstrip-section {
  display: flex;
  justify-content: space-between;
  padding: 10px 6px 4px;
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.tabstrip-section-count {
  opacity: 0.7;
}

.tabstrip-row {
  display: grid;
  /* name+host | bookmark | close */
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  border-radius: 7px;
}

.tabstrip-row:hover {
  background: var(--bg-inset);
}

.tabstrip-open {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding: 6px 8px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* A pending row is not clickable: the tab it stands for does not exist yet. */
.tabstrip-open:disabled {
  cursor: default;
}

/* The text column, so the name and host stay stacked while the chip sits beside them. */
.tabstrip-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  min-width: 0;
  flex: 1 1 auto;
}

/* The icon stand-in: a letter, coloured from the host.

   Deliberately NOT a favicon. Fetching one would mean either asking the remote page for
   its `<link rel="icon">` -- unreliable, and defeated by sites like example.org that
   declare `data:,` -- or pointing an `<img>` at the site from the VIEWER's browser, which
   would make the viewer contact every open site from their own address. That is exactly
   what a cloud browser exists to prevent, so the icon is derived locally instead. */
.tabstrip-chip {
  flex: 0 0 18px;
  width: 18px;
  height: 18px;
  border-radius: 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--text);
  background: var(--border);
  text-transform: uppercase;
}

/* A tab still being created: the letter becomes a ring that turns. */
.tabstrip-chip.is-spinning {
  background: transparent;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  animation: tabstrip-spin 700ms linear infinite;
}

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

/* The tab that is actually in front.

   A different thing from the keyboard selection below, and usually a different row, so
   the two get different treatments: an accent bar down the left edge and a tint, which
   read at a glance even in a long history list. */
.tabstrip-row.is-current {
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  box-shadow: inset 2px 0 0 var(--accent);
}

.tabstrip-row.is-current .tabstrip-name {
  color: var(--text);
  font-weight: 500;
}

/* A row whose tab has been asked for but has not arrived. */
.tabstrip-row.is-pending .tabstrip-name {
  color: var(--text-muted);
}

/* The row the KEYBOARD is on.

   Deliberately stronger than :hover, because the two mean different things and can be
   true at once: a viewer arrowing through the list needs to see where Enter will land
   even with the pointer resting somewhere else entirely. */
.tabstrip-row.is-selected {
  background: var(--border);
  outline: 1px solid var(--accent);
  outline-offset: -1px;
}

.tabstrip-name {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12.5px;
  color: var(--text-muted);
}

.tabstrip-host {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 10.5px;
  color: var(--text-muted);
}

/* Recently closed is history, not something that is running: dimmed, and its chip left NEUTRAL
   rather than carrying the host's colour. The colour is set inline in tabstrip.js, which skips it
   for this kind, so the plain --border background from .tabstrip-chip is what shows through.

   The text and chip are dimmed rather than the whole row, because the star and close buttons on
   that row are just as clickable as anywhere else and dimming the row would have faded them too. */
.tabstrip-row[data-kind="closed"] .tabstrip-name,
.tabstrip-row[data-kind="closed"] .tabstrip-host {
  color: color-mix(in srgb, var(--text-muted) 62%, transparent);
}

.tabstrip-row[data-kind="closed"] .tabstrip-chip {
  background: var(--border);
  color: var(--text-muted);
}

.tabstrip-icon {
  padding: 6px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}

.tabstrip-icon:hover:not(:disabled) {
  color: var(--text);
  background: var(--border);
}

.tabstrip-star.is-on {
  color: var(--warn);
}

.tabstrip-star:disabled {
  opacity: 0.35;
  cursor: default;
}

.tabstrip-empty {
  margin: 0;
  padding: 10px 8px;
  font-size: 12px;
  color: var(--text-muted);
}

/* "What Enter will do" when no row is highlighted, which is the only case where the
   key does something other than activate a match. */
.tabstrip-hint {
  color: var(--accent);
}
