/** Shared presentation for @infraternal/ui browser primitives. */

/* One type scale per platform (TASTE.md section 2). Desktop follows the macOS sizes, touch the iOS
   sizes. Every product surface (top bar, sidebar, palette, tooltip, toast, windows) uses these
   tokens rather than its own pixel values. --row is the list row height (macOS sidebar 28 px, iOS 44 px). --text-input stays 16 px on touch so iOS never zooms. */
:root { --ui-base: 1.1; font-size: calc(16px * var(--ui-base) * var(--ui-scale, 1)); --ui-press-scale: 0.97; --ui-press-scale-small: 0.94; --ui-press-scale-soft: 0.98; --text-body: 0.8125rem; --text-secondary: 0.75rem; --text-caption: 0.6875rem; --text-input: 0.8125rem; --row: 1.75rem; }
/* The UI's base size (owner, 2026-11-01: "110% should be the new 100%"): a desktop's UI is drawn at 1.1 times the 16 px root and
   the UI size slider (--ui-scale, 50-150 %) is relative to it. A touch screen keeps 1: it already has its own larger sizes below
   (16 px text, 44 px rows) and its width is the screen's. Phones taking the 1.1 as well is this one number. */
@media (pointer: coarse) { :root { --ui-base: 1; } }
@media (pointer: coarse) { :root { --text-body: 1rem; --text-secondary: 0.9375rem; --text-caption: 0.8125rem; --text-input: 1rem; --row: 2.75rem; } }

/* Focus rings appear only while the person navigates with the keyboard (focus-ring.ts sets
   data-focus-ring="on"); clicks, taps and shortcuts that move focus in script draw none. */
:focus-visible { outline: none; }
/* The ring is drawn outside its element and reaches 4 px past it (2 px line + 2 px offset). A container that
   clips its overflow (overflow hidden, a height glide) must leave --focus-ring-room on every side of the
   fields it holds, or the ring is cut off where a field touches the container's edge. 6 px is the reach plus
   2 px for anti-aliasing. focus-ring-clip.spec.ts measures every ring against every clipping ancestor. */
:root { --focus-ring-room: 0.375rem; }
[data-focus-ring="on"] :focus-visible { outline: 2px solid var(--focus-ring, var(--accent, #7a9cff)); outline-offset: 2px; }
.countup { font-variant-numeric: tabular-nums; }
.busy-count { font: var(--fw-label, 500) 0.8125rem/1 var(--font-face, "Maple Mono NF", ui-monospace, monospace); color: rgb(0 0 0 / 0.6); }
/* Icons (icons.ts) follow the product's icon scale: 16 px on desktop, 18 px on touch, 1.5 stroke. */
.ui-icon { width: 1rem; height: 1rem; flex: none; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
@media (pointer: coarse) { .ui-icon { width: 1.125rem; height: 1.125rem; } }
.terminal-text-overlay { position: absolute; z-index: 2; pointer-events: none; }

/* The card tint is read by the element that carries it and by nothing inside it, so it does not inherit: a change then
   restyles the glass surfaces and not their descendants too (themes.ts `installLevelRules` sets it per element). */
@property --overlay { syntax: "*"; inherits: false; }
@property --ui-overlay-halo { syntax: "*"; inherits: false; }
@property --ui-overlay-blur { syntax: "*"; inherits: false; }

/* The one material's blur and saturation: surfaces and pills read these two tokens, so tuning them
   here tunes every floating thing at once. */
:root { --ui-overlay-blur: 1rem; --ui-overlay-saturation: 160%; }

/* One theme-aware glass material for every floating surface (D72, D160): popovers, sheets,
   palette, sidebar and tooltips, never in-page controls. No top-edge highlight (TASTE §1).
   Instances can tune the blur, saturation, border or shadow through custom properties. */
.ui-overlay-surface, .ui-panel {
  background: var(--ui-overlay-background, var(--overlay));
  border: 1px solid var(--ui-overlay-border, var(--overlay-line));
  box-shadow: var(--ui-overlay-shadow, var(--shadow));
  color: var(--ui-overlay-color, var(--fg));
  /* A soft halo in the tint colour keeps text legible over a dense terminal without adding tint. */
  text-shadow: 0 0 1px var(--ui-overlay-halo, transparent), 0 0 2px var(--ui-overlay-halo, transparent), 0 0 0.1875rem var(--ui-overlay-halo, transparent), 0 0 0.25rem var(--ui-overlay-halo, transparent), 0 0 0.375rem var(--ui-overlay-halo, transparent), 0 0 0.625rem var(--ui-overlay-halo, transparent);
  -webkit-backdrop-filter: blur(var(--ui-overlay-blur)) saturate(var(--ui-overlay-saturation));
  backdrop-filter: blur(var(--ui-overlay-blur)) saturate(var(--ui-overlay-saturation));
}
/* On the thin material secondary text is a step stronger than on a page (contrast over a busy terminal comes
   from text colour and the halo, never from more tint). */
.ui-overlay-surface, .ui-panel { --ui-text-secondary: color-mix(in oklab, var(--fg) 80%, var(--bg)); }
/* Form controls reset text-shadow in the browser's own styles; they take the halo too. */
.ui-overlay-surface :is(button, input, textarea, select) { text-shadow: inherit; }
.ui-overlay-surface[data-overlay-size="large"] { --ui-overlay-shadow: var(--overlay-shadow-large, var(--shadow)); }
/* A tooltip is as wide as its text up to the cap set on .tip (tooltip.ts) and wraps beyond it, never an ellipsis: the
   owner reads the whole label or the tooltip is wrong. Only point previews carrying the per-call two-line attribute
   (a long link) are clamped, and they say so with their own rules below. */
.tip-label, .tip-measure-label { display: block; white-space: normal; overflow-wrap: anywhere; }
.tip-pos[data-tip-lines="2"] { --tip-content-max: min(16.125rem, calc(100vw - 2.375rem)); }
.tip-pos[data-tip-lines="2"] :is(.tip, .tip-sizer) { max-width: calc(var(--tip-content-max) + 1.25rem + 2px); }
.tip-pos[data-tip-lines="2"] :is(.tip-label:not(.is-leaving), .tip-measure-label) { display: -webkit-box; max-height: 2.4em; overflow: hidden; overflow-wrap: anywhere;
  -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; text-overflow: ellipsis; white-space: normal; }
.ui-button {
  min-height: var(--ui-button-height, 2.75rem);
  padding: var(--ui-button-padding, 0 0.75rem);
  border-radius: var(--ui-button-radius, 0.5rem);
  color: var(--fg);
  font: inherit;
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease;
}
/* Optional button kinds share states and motion; instances supply only geometry
   and typography through variables. Plain ui-button callers keep their defaults. */
.ui-button[data-button] {
  display: inline-flex;
  min-width: var(--ui-button-min-width, 0);
  height: var(--ui-button-height, 2.75rem);
  align-items: center;
  justify-content: center;
  gap: var(--ui-button-gap, 0.625rem);
  border: 0;
  font: var(--ui-button-font, inherit);
  letter-spacing: var(--ui-button-letter-spacing, normal);
  transition: var(--ui-button-transition, transform 140ms var(--ease-out, ease), background-color 160ms ease, color 120ms ease);
}
/* A solid fill carries no text halo: the glass halo (above) would glow into the fill around every glyph, the
   "blurred or doubled" label of the disabled and enabled primary alike (TASTE: button text is crisp in every state). */
.ui-button[data-button="primary"] { background: var(--ui-button-background, var(--fg)); color: var(--ui-button-color, var(--bg)); text-shadow: none; }
.ui-button[data-button="secondary"] { background: var(--ui-button-background, none); color: var(--ui-button-color, var(--fg-2, var(--fg))); }
.ui-button[data-button]:active:not(:disabled) { transform: scale(var(--ui-press-scale)); }
.ui-button[data-button]:disabled { cursor: default; }
/* Disabled solid buttons remain opaque over content beneath their surface. */
/* Lower emphasis comes from colour alone. The label keeps at least 3:1 against its fill in both themes: the fill is a
   quiet tint of the foreground, and the label a stronger one (measured in test/top-centre.spec.ts). */
.ui-button[data-button="primary"]:disabled { background: var(--ui-button-disabled-background, color-mix(in oklab, var(--fg) 12%, var(--bg))); color: var(--ui-button-disabled-color, color-mix(in oklab, var(--fg) 58%, var(--bg))); }
@media (hover: hover) and (pointer: fine) { .ui-button[data-button="secondary"]:hover:not(:disabled) { color: var(--ui-button-hover-color, var(--fg)); } }
.ui-source-icon { display: block; width: 1rem; height: 1rem; flex: none; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }

/* One native switch keeps pointer, keyboard, and assistive-technology state aligned. */
.ui-switch {
  position: relative;
  display: inline-block;
  width: 3rem;
  height: 2.875rem;
  flex: none;
  margin: 0;
  border: 0;
  border-radius: 999px;
  appearance: none;
  background: transparent;
  color: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ui-switch::before, .ui-switch::after {
  position: absolute;
  top: 50%;
  content: "";
  transform: translateY(-50%);
}
.ui-switch::before {
  left: 1px;
  width: 2.875rem;
  height: 1.625rem;
  border: 1px solid var(--overlay-line);
  border-radius: 999px;
  background: var(--overlay-solid);
  transition: background 180ms ease-out, border-color 180ms ease-out;
}
.ui-switch::after {
  left: 0.25rem;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: var(--fg-2);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.18);
  transition: transform 180ms cubic-bezier(0.23, 1, 0.32, 1), background 180ms ease-out;
}
.ui-switch:checked::before { border-color: var(--accent); background: var(--accent); }
.ui-switch:checked::after { transform: translate(1.25rem, -50%); background: var(--bg); }
.ui-switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ui-switch:disabled { cursor: default; opacity: 0.5; }

/* The shared toast can carry a safe, contextual action without cloning overlay material. */
.ui-toast {
  position: fixed;
  z-index: 900;
  right: max(0.75rem, env(safe-area-inset-right));
  bottom: calc(max(1rem, env(safe-area-inset-bottom)) + var(--ui-toast-stack-offset, 0px));
  left: max(0.75rem, env(safe-area-inset-left));
  display: flex;
  width: max-content;
  max-width: min(32.5rem, calc(100% - 1.5rem));
  min-height: 3.375rem;
  align-items: center;
  gap: 0.75rem;
  margin-inline: auto;
  padding: 0.4375rem 0.5625rem 0.4375rem 0.75rem;
  border-radius: 0.9375rem;
  opacity: 1;
  /* No transform at rest: settled toast text stays on the pixel grid. */
  transform: none;
  transform-origin: center bottom;
  transition: opacity 170ms ease-out, transform 190ms cubic-bezier(0.23, 1, 0.32, 1);
}
/* A toast is a small card of its own, not text over a busy terminal: the material's halo would only smear its glyphs. */
.ui-toast, .ui-toast * { text-shadow: none; }
.ui-toast[hidden] { display: none !important; }
.ui-toast[data-open="false"] { opacity: 0; transform: translateY(0.5rem) scale(0.97); pointer-events: none; }
/* One type scale per platform (docs/design/TASTE.md): desktop 13 px body, touch 16 px. */
.ui-toast { min-height: 2.75rem; gap: 0.625rem; padding: 0.375rem 0.5rem 0.375rem 0.75rem; border-radius: 0.875rem; }
/* A long message with actions stacks them under it (toast.ts sets data-stacked) instead of squeezing the text beside them. */
/* Explicit cells: mark | message | close on the first row, the actions on the second. Auto-placement left the glyph, text and
   close in separate rows with an empty column between them at phone width. */
.ui-toast[data-stacked="true"] { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: start; column-gap: 0.625rem; row-gap: 0; }
.ui-toast[data-stacked="true"] .ui-toast-marker, .ui-toast[data-stacked="true"] .ui-toast-glyph { grid-column: 1; grid-row: 1; margin-top: 0.25rem; }
.ui-toast[data-stacked="true"] .ui-toast-message { grid-column: 2; grid-row: 1; padding-top: 0.3125rem; padding-bottom: 0.3125rem; }
.ui-toast[data-stacked="true"] .ui-toast-close { grid-column: 3; grid-row: 1; }
.ui-toast[data-stacked="true"] .ui-toast-actions { grid-column: 2 / -1; grid-row: 2; justify-self: end; }
.ui-toast-message { min-width: 0; color: var(--fg); font-size: var(--text-body); font-weight: var(--fw-body); line-height: 1.4; }
.ui-toast-marker { display: grid; width: 1.375rem; height: 1.375rem; flex: none; place-items: center; }
.ui-toast-marker .tick { width: 1.25rem; height: 1.25rem; }
.ui-toast-marker .tick-fill { background: var(--ok); }
.ui-toast-marker .tick svg { width: 0.875rem; height: 0.875rem; stroke: var(--bg); }
.ui-toast-actions { display: flex; flex: none; align-items: center; gap: 2px; }
.ui-toast-actions[hidden] { display: none !important; }
.ui-toast-action { min-height: 2rem; flex: none; padding: 0 0.5rem; border-radius: 0.5rem; color: var(--accent) !important; font-size: var(--text-body); font-weight: var(--fw-label); white-space: nowrap; }
.ui-toast-action[hidden] { display: none !important; }
/* Icon choices after the message: the same 28 px desktop / 44 px touch size as the confirmation popover's icon buttons, flat and quiet. */
/* A question with choices gets room for its sentence beside them; on a phone it wraps inside the screen width. */
.ui-toast[data-choices="true"] { max-width: min(40rem, calc(100% - 1.5rem)); }
/* On a phone the 44 px buttons would squeeze the sentence into a narrow column: the choices take a row of their own under it. */
@media (max-width: 520px) {
  .ui-toast[data-choices="true"] { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: start; column-gap: 0.625rem; row-gap: 0; }
  .ui-toast[data-choices="true"] .ui-toast-glyph, .ui-toast[data-choices="true"] .ui-toast-marker { grid-column: 1; grid-row: 1; margin-top: 0.25rem; }
  .ui-toast[data-choices="true"] .ui-toast-message { grid-column: 2; grid-row: 1; padding-top: 0.3125rem; }
  .ui-toast[data-choices="true"] .ui-toast-icons { grid-column: 1 / -1; grid-row: 2; justify-self: end; }
}
.ui-toast-icons { display: flex; flex: none; align-items: center; gap: 2px; }
.ui-toast-icons[hidden] { display: none !important; }
.ui-toast-icon { display: grid; width: 1.75rem; min-height: 1.75rem; flex: none; place-items: center; padding: 0; border-radius: 0.5rem; color: var(--fg-2); }
.ui-toast-icon svg { fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.ui-toast-icon:first-child { color: var(--accent); }
@media (hover: hover) and (pointer: fine) { .ui-toast-icon:hover { background: var(--hover); color: var(--fg); } }
.ui-toast-marker .tick { width: 1.125rem; height: 1.125rem; }
/* A glyph in place of the tick (toast icon): neutral, so a waiting state never reads as success. */
.ui-toast-glyph { display: grid; width: 1.375rem; height: 1.375rem; flex: none; place-items: center; color: var(--fg-2); }
.ui-toast-glyph[hidden] { display: none; }
.ui-toast[data-tone="error"] .ui-toast-glyph { color: var(--danger); }
.ui-toast-glyph svg { fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.ui-toast[data-icon="true"] .ui-toast-marker { display: none; }
/* The optional dismiss control: a quiet icon, the same height as the action. */
.ui-toast-close { display: grid; width: 2rem; min-height: 2rem; flex: none; place-items: center; padding: 0; border-radius: 0.5rem; color: var(--fg-3); }
.ui-toast-close[hidden] { display: none; }
.ui-toast-close svg { fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; }
@media (hover: hover) and (pointer: fine) { .ui-toast-close:hover { background: var(--hover); color: var(--fg); } }
@media (pointer: coarse) {
  .ui-toast-close { width: 2.75rem; min-height: 2.75rem; }
  .ui-toast-icon { width: 2.75rem; min-height: 2.75rem; }
  .ui-toast { min-height: 3.375rem; gap: 0.75rem; padding: 0.4375rem 0.5625rem 0.4375rem 0.75rem; border-radius: 0.9375rem; }
  .ui-toast-action { min-height: 2.75rem; padding: 0 0.625rem; }
  .ui-toast-marker .tick { width: 1.25rem; height: 1.25rem; }
}

@media (pointer: coarse) {
  .terminal-screen .ui-toast {
    bottom: calc(var(--keybar-inset, 0px) + var(--keybar-height, 0px) + var(--keybar-gap, 0px) + 0.625rem + env(safe-area-inset-bottom) + var(--ui-toast-stack-offset, 0px));
  }
}
@media (prefers-reduced-motion: reduce) {
  .ui-switch::before, .ui-switch::after { transition: none; }
  .ui-spinner { animation: none; }
  .ui-toast { transition: opacity 150ms ease; transform: none; }
  .ui-toast[data-open="false"] { transform: none; }
}

@media (prefers-reduced-transparency: reduce) {
  .ui-overlay-surface {
    --ui-overlay-background: color-mix(in srgb, var(--overlay) 55%, var(--bg));
    --ui-overlay-blur: 0px;
    --ui-overlay-saturation: 100%;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
@media (prefers-contrast: more) {
  .ui-overlay-surface { --ui-overlay-border: color-mix(in srgb, var(--fg) 72%, transparent); }
}

/* One spring drives the fill, tick draw-on and label treatment. */
.tick { position: relative; display: inline-grid; place-items: center; flex: none; width: 1.25rem; height: 1.25rem;
  border-radius: 0.375rem; box-shadow: inset 0 0 0 var(--ui-tick-border-width, 1.5px) var(--ui-tick-border-color, rgb(255 255 255 / 0.35)); color: #000; will-change: transform; }
.tick-fill { position: absolute; inset: 0; border-radius: inherit; background: var(--fg); transform: scale(0); will-change: transform; }
.tick svg { position: relative; width: 78%; height: 78%; overflow: visible; }
.tick path { fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1; stroke-dashoffset: 1; }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; opacity: 0; appearance: none; -webkit-appearance: none; margin: 0; }
.check { position: relative; transition: transform 140ms var(--ease); }
.check[data-pressed] { transform: scale(0.98); }
[data-focus-ring="on"] .check input:focus-visible + .tick { outline: 2px solid rgb(255 255 255 / 0.6); outline-offset: 2px; }

/* One reusable spinner mark; hosts tune size and color through instance variables. */
.ui-spinner {
  display: inline-block;
  width: var(--ui-spinner-size, 1rem);
  height: var(--ui-spinner-size, 1rem);
  flex: none;
  border: var(--ui-spinner-border-width, 1.5px) solid color-mix(in oklab, var(--ui-spinner-color, var(--accent)) 30%, transparent);
  border-top-color: var(--ui-spinner-color, var(--accent));
  border-radius: 50%;
  animation: ui-spinner-turn 720ms linear infinite;
}
.ui-spinner[hidden] { display: none !important; }
@keyframes ui-spinner-turn { to { transform: rotate(1turn); } }

/* A quiet, non-interactive step mark composes shared spinner and tick behavior. */
.ui-step-status {
  --ui-step-status-size: 1.25rem;
  display: inline-grid;
  width: var(--ui-step-status-size);
  height: var(--ui-step-status-size);
  flex: none;
  place-items: center;
  color: var(--fg-3);
}
.ui-step-status > .ui-step-status-pending,
.ui-step-status > .ui-step-status-tick,
.ui-step-status > .ui-step-status-failed,
.ui-step-status > .ui-spinner { grid-area: 1 / 1; }
.ui-step-status-pending {
  width: var(--ui-step-status-dot-size, 0.4375rem);
  height: var(--ui-step-status-dot-size, 0.4375rem);
  border: 1.5px solid currentColor;
  border-radius: 50%;
}
.ui-step-status-tick { display: none; width: 1.125rem; height: 1.125rem; color: var(--bg); }
.ui-step-status-tick .tick-fill { background: var(--ok); }
.ui-step-status-tick .tick path { stroke: var(--bg); }
.ui-step-status-failed { display: none; width: 0.9375rem; height: 0.9375rem; fill: none; stroke: var(--danger); stroke-width: 1.8; stroke-linecap: round; }
.ui-step-status[data-state="running"] .ui-step-status-pending,
.ui-step-status[data-state="running"] .ui-step-status-tick,
.ui-step-status[data-state="running"] .ui-step-status-failed,
.ui-step-status[data-state="done"] .ui-step-status-pending,
.ui-step-status[data-state="done"] .ui-step-status-failed,
.ui-step-status[data-state="failed"] .ui-step-status-pending,
.ui-step-status[data-state="failed"] .ui-step-status-tick { display: none; }
.ui-step-status[data-state="done"] .ui-step-status-tick { display: grid; }
.ui-step-status[data-state="failed"] .ui-step-status-failed { display: block; }

/* Glitch text is monochrome and settles after its welcome interval. */
.glitch { position: relative; display: inline-block; white-space: nowrap; }
.glitch::before, .glitch::after { content: attr(data-text); position: absolute; top: 0; left: 0; width: 100%; color: inherit; background: #000; overflow: hidden; clip-path: inset(0 0 0 0); pointer-events: none; }
.glitch::after { transform: translateX(0.375rem); text-shadow: var(--g-after-shadow); animation: ui-glitch-bands var(--g-after-dur, 1.5s) infinite linear alternate-reverse; }
.glitch::before { transform: translateX(-0.375rem); text-shadow: var(--g-before-shadow); animation: ui-glitch-bands var(--g-before-dur, 1s) infinite linear alternate-reverse; }
.glitch.is-settled::before, .glitch.is-settled::after { animation: none; opacity: 0; transition: opacity 260ms var(--ease); }
@keyframes ui-glitch-bands {
  0% { clip-path: inset(20% 0 50% 0); } 10% { clip-path: inset(15% 0 55% 0); } 20% { clip-path: inset(30% 0 40% 0); }
  30% { clip-path: inset(10% 0 60% 0); } 40% { clip-path: inset(25% 0 35% 0); } 50% { clip-path: inset(40% 0 20% 0); }
  60% { clip-path: inset(15% 0 55% 0); } 70% { clip-path: inset(30% 0 40% 0); } 80% { clip-path: inset(20% 0 50% 0); }
  90% { clip-path: inset(25% 0 35% 0); } 100% { clip-path: inset(30% 0 40% 0); }
}

/* DECISION 103: one glyph cell morphs to a tick for 900 ms, then returns. */
.icon-action { display: inline-grid; place-items: center; }
.swap { display: inline-grid; width: 1.0625rem; height: 1.0625rem; }
.swap svg { grid-area: 1 / 1; width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; transition: opacity 150ms var(--ease), transform 150ms var(--ease), filter 150ms var(--ease); }
.swap .is-done { opacity: 0; transform: scale(0.6); filter: blur(2px); stroke-width: 2.2; }
.swap .is-done path { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 0ms 150ms; }
[data-done] { color: var(--fg); }
[data-done] svg { transition-duration: 200ms; }
[data-done] .is-idle { opacity: 0; transform: scale(0.6); filter: blur(2px); }
[data-done] .is-done { opacity: 1; transform: none; filter: none; }
[data-done] .is-done path { stroke-dashoffset: 0; transition: stroke-dashoffset 260ms var(--ease) 50ms; }
[data-instant] .swap svg, [data-instant] .is-done path { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .check[data-pressed] { transform: none; transition: none; }
  .swap svg, [data-done] .is-idle, .swap .is-done, [data-done] .is-done { transform: none; filter: none; }
  .swap .is-done path { stroke-dashoffset: 0; transition: none; }
}

/* ---------------------------------------------------------------------------------------
   Type roles, taken from the sign-in card's computed values (the owner's benchmark,
   docs/design/TASTE.md §2): the same on desktop and touch. Only row and control heights
   follow the platform (macOS scale on desktop, 44 px targets on touch).
     name    600 14px/1     -0.01em  (auth-mark)
     body    450 14px/1.55  -0.01em  (auth-lede)
     label   450 13px/1.55  -0.01em  (auth-label)
     button  500 13.5px/1            (auth-back, auth-text-button)
     eyebrow 500 12px/1     +0.02em  (auth-eyebrow)
   --------------------------------------------------------------------------------------- */
:root {
  --ui-type-name: 600 0.875rem/1 var(--font-ui, "Geist Mono", ui-monospace, monospace);
  --ui-type-body: 450 0.875rem/1.55 var(--font-ui, "Geist Mono", ui-monospace, monospace);
  --ui-type-label: 450 0.8125rem/1.55 var(--font-ui, "Geist Mono", ui-monospace, monospace);
  /* Line height `normal`, never a number: next to an icon, a label's line box must be the font's own height. With a smaller
     line height (it was 1) Blink rounds the half-leading asymmetrically and the glyphs sit up to 0.75 px above the centre the
     icon is centred on (TASTE "Icon and text share one centre line", #282; icon-text-centre.spec.ts measures it). Every control
     that uses this token has a fixed height or min-height and centres its content, so only the text's offset changes. */
  --ui-type-button: 500 0.84375rem/normal var(--font-ui, "Geist Mono", ui-monospace, monospace);
  --ui-type-eyebrow: 500 0.75rem/1 var(--font-ui, "Geist Mono", ui-monospace, monospace);
  /* Every section heading (the small uppercase titles above a group of rows) is padded with this one value:
     6 px above, --ui-heading-gap between it and the group's first row. A new heading style uses it, never its own numbers. */
  --ui-heading-gap: 0.5rem;
  --ui-heading-pad: 0.375rem 0 var(--ui-heading-gap);
  --ui-tracking: -0.01em;
  /* Secondary text matches the sign-in card's secondary (rgb 161 161 170 on ink) and
     adapts to every theme, instead of the dimmer --fg-2. */
  --ui-text-secondary: color-mix(in oklab, var(--fg) 70%, var(--bg));
  --ui-row-h: 2rem;
  --ui-control-h: 1.875rem;
  --ui-icon: 1rem;
}
@media (pointer: coarse) {
  :root { --ui-row-h: 2.75rem; --ui-control-h: 2.75rem; --ui-icon: 1.125rem; }
}

/* Window card (window-card.ts): the pane card's surface plus a quiet header. */
.ui-window {
  position: relative; display: flex; flex-direction: column; min-width: 0; min-height: 0;
  /* Not clipped: the lock's shackle rises over the frame line. The body clips its own content. */
  background: var(--bg);
  border: 1px solid var(--overlay-line); border-radius: var(--r-lg, 1.125rem); box-shadow: var(--shadow);
  transition: border-color 180ms var(--ease-out, ease-out);
}
/* Window transparency (themes.ts WINDOW_LEVELS): Solid is the line above, with no alpha and no filter. The other
   levels read one fill token; only Frosted adds a backdrop blur, and only on windows that are on screen
   (canvas/memory.ts sets data-onscreen), because many large blurred windows cost frames and memory. */
/* The fills and the Frosted blur are literal rules written from WINDOW_LEVELS (themes.ts `installLevelRules`). */
/* Solid cards paint no blur at all. */
:root[data-card="solid"] .ui-overlay-surface, :root[data-card="solid"] .ui-panel, :root[data-card="solid"] .ui-pill { -webkit-backdrop-filter: none; backdrop-filter: none; }
.ui-window[data-active] { border-color: color-mix(in oklab, var(--accent) 50%, var(--overlay-line)); }
.ui-window-head { display: flex; flex: none; align-items: center; gap: 0.5rem; padding: 0.5rem 0.5rem 0 0.5rem; -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
.ui-window-pill {
  display: inline-flex; align-items: center; gap: 0.5rem; min-width: 0; height: var(--ui-control-h); padding: 0 0.625rem 0 0.5rem;
  border: 1px solid var(--overlay-line); border-radius: 999px; background: transparent; color: var(--fg);
  font: var(--ui-type-button); letter-spacing: var(--ui-tracking); white-space: nowrap; cursor: pointer;
  /* The lock owns the exact centre of the frame line: the pill never reaches past half the header minus the lock's hit area. */
  max-width: calc(50% - 1.625rem); line-height: 1.4; /* room for descenders: the title clips its overflow */
  transition: background-color 140ms ease, transform 140ms var(--ease-out, ease-out);
}
.ui-window-pill:active { transform: scale(var(--ui-press-scale)); }
.ui-window-mark { display: grid; flex: none; place-items: center; width: var(--ui-icon); height: var(--ui-icon); color: var(--fg-2); }
.ui-window-mark svg { width: 100%; height: 100%; fill: currentColor; stroke: none; }
/* Line glyphs (a flow's mark, not a host platform) draw as the 1.5 stroke icons everywhere else. */
.ui-window-mark svg.is-line { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
/* The setup ring (setup-ring.ts) sits over the mark's system icon and has exactly the mark's size, so the pill never changes
   width. Opacity and transform only; `data-setup` on the mark says which of ring, tick, hidden (a flight carries a copy)
   or rest the slot is in, and hides the icon underneath until the ring is done. */
.ui-window-mark { position: relative; }
.ui-window-mark[data-setup] > svg { transition: opacity 220ms cubic-bezier(0.23, 1, 0.32, 1); }
.ui-window-mark[data-setup="ring"] > svg, .ui-window-mark[data-setup="tick"] > svg, .ui-window-mark[data-setup="hidden"] > svg { opacity: 0; }
.ui-setup-ring { position: absolute; inset: 0; display: block; color: var(--fg-2); pointer-events: none; transition: opacity 220ms cubic-bezier(0.23, 1, 0.32, 1); }
.ui-setup-ring[data-hidden="true"], .ui-setup-ring[data-phase="rest"] { opacity: 0; }
.ui-setup-ring[data-instant] { transition: none; }
.ui-setup-ring > svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; fill: none; }
.ui-setup-arcs { transition: opacity 150ms cubic-bezier(0.23, 1, 0.32, 1), transform 150ms cubic-bezier(0.23, 1, 0.32, 1); }
.ui-setup-track, .ui-setup-fill { fill: none; stroke: currentColor; stroke-width: 3; stroke-linecap: round; }
.ui-setup-track { opacity: 0.25; }
.ui-setup-fill { opacity: 0; transition: opacity 220ms cubic-bezier(0.23, 1, 0.32, 1), stroke 160ms ease; }
.ui-setup-fill[data-state="done"] { opacity: 1; stroke: var(--fg); }
.ui-setup-fill[data-state="running"] { opacity: 0.55; animation: ui-setup-pulse 1100ms ease-in-out infinite alternate; }
.ui-setup-fill[data-state="failed"] { opacity: 1; stroke: var(--danger); }
@keyframes ui-setup-pulse { from { opacity: 0.3; } to { opacity: 0.9; } }
.ui-setup-check { opacity: 0; transform: scale(0.7); transition: opacity 150ms cubic-bezier(0.23, 1, 0.32, 1), transform 150ms cubic-bezier(0.23, 1, 0.32, 1); }
.ui-setup-check path { fill: none; stroke: var(--ok); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 150ms cubic-bezier(0.23, 1, 0.32, 1); }
.ui-setup-ring[data-phase="tick"] .ui-setup-arcs, .ui-setup-ring[data-phase="rest"] .ui-setup-arcs { opacity: 0; transform: scale(0.7); }
.ui-setup-ring[data-phase="tick"] .ui-setup-check, .ui-setup-ring[data-phase="rest"] .ui-setup-check { opacity: 1; transform: none; }
.ui-setup-ring[data-phase="tick"] .ui-setup-check path, .ui-setup-ring[data-phase="rest"] .ui-setup-check path { stroke-dashoffset: 0; }
/* A flying copy of the ring (setup-pill.on-demand.ts) is a fixed box moved by transform only. */
.ui-setup-fly { position: fixed; left: 0; top: 0; z-index: 90; pointer-events: none; transform-origin: 0 0; color: var(--fg); }
@media (prefers-reduced-motion: reduce) {
  .ui-setup-fill[data-state="running"] { animation: none; opacity: 0.55; }
  .ui-setup-arcs, .ui-setup-check { transition: opacity 120ms ease; }
  .ui-setup-ring[data-phase="tick"] .ui-setup-arcs, .ui-setup-ring[data-phase="rest"] .ui-setup-arcs, .ui-setup-check { transform: none; }
  .ui-setup-check path { transition: none; }
  .ui-setup-fill, .ui-setup-ring, .ui-window-mark[data-setup] > svg { transition-duration: 120ms; }
}
.ui-window-title { display: inline-flex; min-width: 0; overflow: hidden; }
.ui-window-user { color: var(--ui-text-secondary); font-weight: var(--fw-secondary); }
.ui-window-host { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* The title gives way before the actions do, and the session name before the host: it shrinks a hundred times faster. */
.ui-window-title { flex: 0 1 auto; }
.ui-window-session { flex: 0 100 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--fg-3); font-weight: var(--fw-secondary); }
/* In a narrow header the login goes first (it is the quietest part of the name). */
.ui-window-head { container-type: inline-size; }
@container (max-width: 35rem) { .ui-window-user { display: none; } }
.ui-window-session::before { content: "·"; margin-right: 0.5rem; color: var(--fg-3); }
/* The window's connection indicator (window-card.ts, connection-indicator.ts): the pill's status, in the place the dot had.
   It is the same icon as the top pill's, 14 px here so it sits level with the pill's text; a window that is not a link yet
   (a flow in progress) shows none. */
.ui-window-conn { --ui-conn-size: 0.875rem; }
/* On touch the pill's hit area (`::after`, 44 px tall) is drawn over its children; the icon stays above it so a tap on it shows its sentence. */
.ui-window-pill .ui-window-conn { z-index: 1; }
.ui-window[data-status="none"] .ui-window-conn { display: none; }
/* The header's actions are one pill (the shared material, without its blur: windows stay cheap) holding icon
   buttons, the same height as the title pill and on the same line. The pill never shrinks (the title truncates
   first). On touch both pills are 36 px tall and each button reaches a 44 px square through an invisible hit area (see below). */
.ui-window-actions {
  display: flex; flex: none; align-items: center; gap: 2px; margin-left: auto; height: var(--ui-control-h); padding: 0 2px;
  border: 1px solid var(--overlay-line); border-radius: 999px; background: var(--overlay); box-shadow: none;
}
.ui-window-actions:empty { display: none; }
.ui-window-action {
  position: relative; display: grid; place-items: center; width: calc(var(--ui-control-h) - 0.375rem); height: calc(var(--ui-control-h) - 0.375rem); padding: 0; border: 0; border-radius: 50%;
  background: none; color: var(--fg-3); cursor: pointer; transition: background-color 140ms ease, color 140ms ease, transform 140ms var(--ease-out, ease-out);
}
/* On touch there is no frame lock to leave room for, so the pill may use the whole header less the actions: the host's name is
   never cut while there is room. The session name is secondary and goes whole (never as a lone "· …") in a phone-width card. */
@media (pointer: coarse) {
  .ui-window-pill { max-width: 100%; }
  @container (max-width: 25rem) { .ui-window-session { display: none; } }
  /* One visible size for every header pill (TASTE: window header pills are one size): the title pill and the actions pill are
     36 px tall, and the 44 px touch target comes from invisible padding around each (::after), never from a bigger pill. */
  .ui-window-pill, .ui-window-actions { height: 2.25rem; }
  .ui-window-pill { position: relative; }
  /* The header is a container (layout containment makes it its own stacking level), so it must sit above the body itself:
     otherwise the body covers the 4 px of hit area below the pill and the pill is a 40 px target, not 44. */
  .ui-window-head { position: relative; z-index: 1; }
  .ui-window-pill::after { content: ""; position: absolute; inset: -0.375rem -0.1875rem; }
  .ui-window-actions { gap: 0; padding: 0 0.1875rem; position: relative; z-index: 1; } /* above the body, so the hit area below the pill is not covered by it */
  .ui-window-action { width: 1.875rem; height: 1.875rem; }
  .ui-window-action::after { content: ""; position: absolute; inset: -0.4375rem; } /* 44 px square around the 30 px button */
}
.ui-window-action:active { transform: scale(var(--ui-press-scale)); }
/* A column at the weight floor is only a few dozen pixels wide (a layout drawn on a wide screen, shown on a phone).
   Chrome never overlaps or clips there: the title text goes first, then the whole title pill, then the actions. The
   window itself stays reachable through the canvas row and the search palette. */
@container (max-width: 9.375rem) { .ui-window-title, .ui-window-conn { display: none; } }
@container (max-width: 5.25rem) { .ui-window-pill { display: none; } }
@container (max-width: 2.5rem) { .ui-window-actions { display: none; } }
.ui-window-action svg { width: var(--ui-icon); height: var(--ui-icon); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
/* Title first (TASTE): in a header this narrow the actions give way to one ⋯, so the title keeps its room. A card with one
   action never collapses (the actions container says so with data-collapsible). The ⋯ opens the same actions in a menu. */
.ui-window-action.ui-window-more { display: none; }
@container (max-width: 20rem) {
  .ui-window-actions[data-collapsible] > .ui-window-action:not(.ui-window-more) { display: none; }
  .ui-window-actions[data-collapsible] > .ui-window-action.ui-window-more { display: grid; }
  /* The title's room is the pill's half of the header: the window's mark and session name give way first, at any narrow
     width (a half on a phone too), so the title is never squeezed out. */
  .ui-window-session { display: none; }
  /* The mark names the machine's system and leads the pill on a phone, whatever the width (TASTE: the window pill names the machine by its system). */
  @media (pointer: fine) { .ui-window-mark { display: none; } }
}
/* The anchored popover (anchored-popover.ts) floats inside its stage at the coordinates the placement computes. */
.ui-anchored-popover { position: absolute; z-index: 6; overflow: hidden; border-radius: 0.875rem; }
.ui-window-menu { display: flex; flex-direction: column; gap: 2px; min-width: 10.5rem; padding: 0.375rem; }
.ui-window-menu-item {
  display: flex; align-items: center; gap: 0.625rem; width: 100%; height: 2.125rem; padding: 0 0.625rem; border: 0; border-radius: 0.625rem;
  background: none; color: var(--fg-2); font: var(--ui-type-button); letter-spacing: var(--ui-tracking); white-space: nowrap; text-align: left; cursor: pointer;
}
.ui-window-menu-item svg { width: var(--ui-icon); height: var(--ui-icon); flex: none; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.ui-window-menu-rule { height: 1px; margin: 0.1875rem 0.375rem; background: var(--line); }
.ui-window-menu-item.is-danger { color: var(--danger); }
/* A menu opened from the keyboard lands on its first row without a ring (rings are for Tab, TASTE 1); a quiet fill shows where it is. */
.ui-window-menu-item:focus-visible { background: var(--hover); color: var(--fg); }
.ui-window-menu-item.is-danger:focus-visible { color: var(--danger); }
@media (pointer: coarse) { .ui-window-menu-item { height: 2.75rem; } }
@media (hover: hover) and (pointer: fine) { .ui-window-menu-item:hover { background: var(--hover); color: var(--fg); } .ui-window-menu-item.is-danger:hover { color: var(--danger); } }
/* Row swipes (row-menu.ts): the row's leaf and the revealed buttons move together with one transform each; the row clips them. */
.ui-swipe-row { position: relative; overflow: hidden; border-radius: 0.5rem; touch-action: pan-y; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
.ui-row-swipe-act { text-shadow: none; position: absolute; top: 0; bottom: 0; display: flex; align-items: center; width: 100%; overflow: hidden; pointer-events: auto; cursor: pointer; background: color-mix(in oklab, var(--fg) 14%, transparent); color: var(--fg); font: var(--ui-type-button); letter-spacing: var(--ui-tracking); }
.ui-row-swipe-act[data-side="left"] { left: 100%; justify-content: flex-start; }
.ui-row-swipe-act[data-side="right"] { right: 100%; justify-content: flex-end; }
.ui-row-swipe-act[data-tone="danger"] { background: var(--danger); color: #fff; }
.ui-row-swipe-in { display: flex; flex: none; box-sizing: border-box; align-items: center; gap: 0.375rem; padding: 0 0.875rem; white-space: nowrap; will-change: transform, opacity; }
/* The cue that a release will act builds over the last stretch of the pull (--arm, 0 to 1, written by row-menu.ts): the
   button's fill takes the accent, and at the threshold the icon pops with a little overshoot. Opacity and transform only. */
.ui-row-swipe-act::before { content: ""; position: absolute; inset: 0; background: var(--accent); opacity: calc(var(--arm, 0) * 0.34); pointer-events: none; }
.ui-row-swipe-act[data-tone="danger"]::before { background: #000; opacity: calc(var(--arm, 0) * 0.18); }
.ui-row-swipe-in { position: relative; }
.ui-row-swipe-icon { display: grid; place-items: center; transition: transform 240ms var(--ease-out, cubic-bezier(0.23, 1, 0.32, 1)); }
.ui-row-swipe-icon svg { width: var(--ui-icon); height: var(--ui-icon); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.ui-row-swipe-act[data-armed] .ui-row-swipe-icon { transform: scale(1.22); }
@media (prefers-reduced-motion: reduce) { .ui-row-swipe-icon { transition: none; } }
.ui-row-edit { flex: 1; min-width: 0; height: 1.375rem; margin: 0 -0.25rem; padding: 0 0.25rem; border: 0; border-radius: 0.375rem; background: var(--hover); color: var(--fg); font: inherit; letter-spacing: inherit; outline: none; }
/* The in-row question (row-confirm.ts): over the row's leaf, at the leaf's own height, so the row never changes size. The leaf
   keeps its box and its marker (the status dot); only its words fade, and the question fades in over them, starting where the
   label started (row-confirm.ts sets the padding from the label's own offset). */
.bm-row[data-asking] { position: relative; }
.bm-row > .bm-leaf { transition: transform 140ms var(--ease-out, ease-out), opacity 150ms var(--ease-out, ease-out); }
.bm-row > .bm-leaf > * { transition: opacity 150ms var(--ease-out, ease-out); }
.bm-row[data-asking] > .bm-leaf { pointer-events: none; }
.bm-row[data-asking] > .bm-leaf > :not(.glyph) { opacity: 0; }
.ui-row-ask { position: absolute; inset: 0; z-index: 2; display: flex; align-items: center; gap: 0; padding: 0 2px 0 0.375rem; opacity: 0; color: var(--fg-2); font: inherit; letter-spacing: inherit; white-space: nowrap; transition: opacity 150ms var(--ease-out, ease-out); }
.ui-row-ask[data-open] { opacity: 1; }
.ui-row-ask:not([data-open]) { pointer-events: none; }
.ui-row-ask[data-instant] { transition: none; }
.ui-row-ask-q { flex: none; margin-right: 0.1875rem; color: var(--fg); font-weight: var(--fw-body, 500); } /* the label's own weight: same text, turned into a question */
.ui-row-ask-effect { position: absolute; left: 0; top: 0; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.ui-row-ask-dot { color: var(--fg-3); }
.ui-row-ask button { position: relative; flex: none; height: calc(100% - 0.375rem); padding: 0 0.1875rem; border: 0; border-radius: 0.375rem; background: none; color: var(--fg-2); font: inherit; font-weight: var(--fw-label, 500); letter-spacing: inherit; cursor: pointer; transition: background-color 120ms ease, color 120ms ease, transform 120ms var(--ease-out, ease-out); }
.ui-row-ask button::after { content: ""; position: absolute; inset: -0.1875rem -0.25rem; } /* the hit area keeps to the row's own height */
.ui-row-ask button:active { transform: scale(var(--ui-press-scale)); }
.ui-row-ask .ui-row-ask-yes { color: var(--danger); }
@media (hover: hover) and (pointer: fine) { .ui-row-ask button:hover { background: var(--hover); color: var(--fg); } .ui-row-ask .ui-row-ask-yes:hover { color: var(--danger); } }
.ui-row-ask button:focus { outline: none; }
[data-focus-ring="on"] .ui-row-ask button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.ui-row-ask button:focus-visible { background: var(--hover); }
@media (pointer: coarse) { .ui-row-ask button { padding: 0 0.375rem; } } /* the 44 px target is the ::after, not the padding: a phone row has 253 px for the question and its answers */
@media (prefers-reduced-motion: reduce) { .ui-row-ask, .bm-row > .bm-leaf, .bm-row > .bm-leaf > * { transition: none; } .ui-row-ask button { transition: none; } }
.bm-row { touch-action: pan-y; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
.ui-window-body { position: relative; flex: 1; min-height: 0; overflow: hidden; border-radius: 0 0 calc(var(--r-lg, 1.125rem) - 1px) calc(var(--r-lg, 1.125rem) - 1px); }
@media (hover: hover) and (pointer: fine) {
  .ui-window-pill:hover { background: var(--hover); }
  .ui-window-action:hover { background: var(--hover); color: var(--fg-2); }
}

/* Capsule tabs (capsule-tabs.ts): flat track, one gliding capsule. */
.ui-tabs { position: relative; display: inline-flex; gap: 2px; padding: 0.1875rem; border-radius: 999px; background: var(--hover); isolation: isolate; }
.ui-tabs-capsule {
  position: absolute; z-index: -1; top: 0.1875rem; bottom: 0.1875rem; left: 0; border-radius: 999px;
  background: var(--overlay-solid); box-shadow: 0 1px 2px rgb(0 0 0 / 0.14), 0 0 0 1px var(--overlay-line);
}
.ui-tab {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.375rem; min-width: 0; height: calc(var(--ui-control-h) - 0.375rem);
  padding: 0 0.75rem; border: 0; border-radius: 999px; background: none; color: var(--fg-2); cursor: pointer;
  font: var(--ui-type-button); letter-spacing: var(--ui-tracking); white-space: nowrap; transition: color 160ms ease;
}
.ui-tab[aria-selected="true"] { color: var(--fg); }
.ui-tabs[data-compact] .ui-tab[aria-selected="false"] > span { display: none; }
.ui-tabs[data-compact] .ui-tab[aria-selected="false"] { padding: 0 0.625rem; }
.ui-tab svg { width: var(--ui-icon); height: var(--ui-icon); flex: none; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }

/* Key/value list (kv-list.ts): keys left, values right, every cell copies. */
.ui-kv { display: flex; flex-direction: column; }
.ui-kv-row {
  display: grid; grid-template-columns: fit-content(55%) minmax(0, 1fr); column-gap: 1rem; align-items: baseline;
  min-height: var(--ui-row-h); padding: 0.25rem 0; border-bottom: 1px solid var(--line);
}
.ui-kv-row:last-child { border-bottom: 0; }
/* A row's pane (`panel` in kv-list.ts) takes the whole row under the key and the value. */
.ui-kv-row > .ui-kv-panel { grid-column: 1 / -1; min-width: 0; }
/* Titled sections inside one list: the quiet eyebrow of the settings headings, no box or rule of its own. */
.ui-kv-section + .ui-kv-section { margin-top: 0.75rem; }
.ui-kv-rows { display: flex; flex-direction: column; }
.ui-kv-group { padding: var(--ui-heading-pad); color: var(--ui-text-secondary); font: var(--ui-type-eyebrow); letter-spacing: 0.02em; text-transform: uppercase; }
.ui-kv-cell {
  display: block; min-width: 0; max-width: 100%; margin: 0; padding: 0; border: 0; background: none; color: inherit; cursor: pointer; text-align: left; font: inherit;
  transition: transform 140ms var(--ease-out, ease-out);
  -webkit-user-select: text; user-select: text; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
.ui-kv-key { justify-self: start; color: var(--ui-text-secondary); font: var(--ui-type-label); letter-spacing: var(--ui-tracking); white-space: nowrap; }
.ui-kv-key { display: grid; justify-items: start; }
.ui-kv-hint { color: var(--fg-3); font: var(--ui-type-label); font-size: 0.6875rem; letter-spacing: var(--ui-tracking); white-space: nowrap; }
.ui-kv-value { justify-self: end; color: var(--fg); font: var(--ui-type-body); letter-spacing: var(--ui-tracking); text-align: right; }
.ui-kv-cell:active { transform: scale(var(--ui-press-scale-soft)); }
/* The highlighted run: 6 px of even padding either side (cancelled by a -6 px margin so the text
   still lines up with the row's edges), cloned across wrapped lines. */
.ui-kv-text {
  display: inline; margin: 0 -0.375rem; padding: 0.1875rem 0.375rem; border-radius: 0.375rem; overflow-wrap: anywhere;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
/* A value that changes (kv-list `patch`) fades out, says its new thing while it is gone, and fades back in. */
.ui-kv-text { transition: background-color 120ms ease, opacity 160ms ease-out; }
.ui-kv-cell[data-swapping] > .ui-kv-text { opacity: 0; transition: background-color 120ms ease, opacity 110ms ease-out; }
/* The copy confirmation (copy-feedback.ts `confirmInPlace`): the text fades out and a clipboard icon with "Copied" fades in
   over the same spot, at the cell's own edge (right for a value, left for a key). The cell keeps its box. Opacity only;
   the icon also grows a little, and with reduced motion it does not. */
.ui-kv-cell { position: relative; }
.ui-kv-cell[data-copied] > .ui-kv-text { opacity: 0; transition: background-color 120ms ease, opacity 110ms ease-out; }
.ui-copied {
  position: absolute; top: 0; display: inline-flex; align-items: center; gap: 0.375rem; padding: 0.1875rem 0; white-space: nowrap; pointer-events: none;
  color: var(--fg); opacity: 0; transition: opacity 120ms ease-out;
}
.ui-kv-value > .ui-copied { right: 0; }
.ui-kv-key > .ui-copied { left: 0; }
.ui-kv-cell[data-copied] > .ui-copied { opacity: 1; transition: opacity 160ms ease-out 40ms; }
.ui-copied-icon { display: inline-grid; place-items: center; width: 0.875rem; height: 0.875rem; transform: scale(0.7); transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1); }
.ui-kv-cell[data-copied] .ui-copied-icon { transform: none; }
.ui-copied-icon svg { width: 0.875rem; height: 0.875rem; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.ui-kv-cell[data-instant] > .ui-kv-text, .ui-kv-cell[data-instant] > .ui-copied, .ui-kv-cell[data-instant] .ui-copied-icon { transition: none; }
@media (prefers-reduced-motion: reduce) { .ui-copied-icon { transform: none; transition: none; } }
/* A value that is its row's action (kv-list `press`) reads as one: it keeps the pointer and takes the press scale. */
.ui-kv-press { cursor: pointer; }
/* A mark before a value (kv-list `mark`), from the shared mark vocabulary: a tick, an exclamation, a quiet cross, a turning arc. */
.ui-kv-mark { display: inline-block; margin-right: 0.375rem; vertical-align: -1px; }
.ui-mark[data-mark="off"], .ui-mark[data-mark="warn"] { background: currentColor; -webkit-mask: var(--ui-mark-icon) center / contain no-repeat; mask: var(--ui-mark-icon) center / contain no-repeat; }
.ui-mark[data-mark="off"] { color: var(--fg-3); --ui-mark-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round'%3E%3Cpath d='M6.5 6.5l11 11M17.5 6.5l-11 11'/%3E%3C/svg%3E"); }
.ui-mark[data-mark="warn"] { color: var(--warn); --ui-mark-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.2' stroke-linecap='round'%3E%3Cpath d='M12 5.5v8.5M12 18.5h.01'/%3E%3C/svg%3E"); }
/* The spinner: an arc turning around a faint ring, the same shape as the connection icon's. Compositor-only; still under reduced motion. */
.ui-mark[data-mark="busy"] { box-sizing: border-box; border: 1.6px solid color-mix(in oklab, currentColor 28%, transparent); border-top-color: currentColor; border-radius: 50%; color: var(--fg-2); animation: ui-mark-spin 800ms linear infinite; }
@keyframes ui-mark-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ui-mark[data-mark="busy"] { animation: none; } }
/* A wide row (kv-list `wide`): no key, the value runs the whole row; a link ends in a quiet chevron. */
.ui-kv-wide > .ui-kv-value { grid-column: 1 / -1; justify-self: stretch; padding-right: 1.25rem; text-align: left; }
.ui-kv-wide:not(:has(.ui-kv-press)) > .ui-kv-value { color: var(--ui-text-secondary); padding-right: 0; }
.ui-kv-wide > .ui-kv-value > .ui-copied { right: auto; left: 0; }
.ui-kv-chevron { position: absolute; right: 0; top: calc(50% - 0.5rem); width: 0.875rem; height: 0.875rem; fill: none; stroke: var(--ui-text-secondary); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.ui-kv-meter { grid-column: 1 / -1; position: relative; height: 0.1875rem; margin: 0.25rem 0 2px; overflow: hidden; border-radius: 2px; background: var(--line); }
.ui-kv-meter::after { content: ""; position: absolute; inset: 0; border-radius: inherit; background: var(--accent); transform: scaleX(var(--meter, 0)); transform-origin: left; }
@media (hover: hover) and (pointer: fine) {
  .ui-kv-cell:hover .ui-kv-text { background: var(--hover); }
}
@media (prefers-reduced-motion: reduce) {
  .ui-window-pill:active, .ui-window-action:active { transform: none; }
}

/* DECISION 120 icon slot for branched-menu hosts. */
.bm-icon { display: grid; place-items: center; width: 1rem; height: 1rem; flex: none; color: var(--fg-2); }
.bm-icon svg { display: block; width: 0.9375rem; height: 0.9375rem; fill: currentColor; stroke: none; }

/* Shared step transitions (pane-stepper.ts): leaving panes stack over the incoming one. */
.ui-pane-viewport { position: relative; }
.ui-pane-viewport.is-moving { overflow: hidden; }
.ui-pane.is-leaving { position: absolute; top: 0; right: 0; left: 0; pointer-events: none; }

/* Chip field (chip-input.ts): chips wrap inside a field that grows with a spring. */
.ui-chip-field {
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 0.75rem;
  background: rgb(from var(--fg) r g b / 0.05);
  cursor: text;
  transition: border-color 120ms ease, background-color 120ms ease;
}
.ui-chip-field:focus-within { border-color: rgb(from var(--fg) r g b / 0.28); background: rgb(from var(--fg) r g b / 0.09); }
.ui-chip-flow { display: flex; min-height: 2.875rem; flex-wrap: wrap; align-items: center; gap: 0.375rem; padding: 0.375rem 0.5rem 0.375rem var(--ui-chip-field-inset, 0.875rem); }
.ui-chip-text {
  min-width: 8ch;
  height: 2rem;
  flex: 1 1 8ch;
  padding: 0;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--fg);
  font: var(--fw-body) 1rem/1 var(--font-ui, "Geist Mono", ui-monospace, monospace);
  letter-spacing: 0.01em;
}
/* With chips present the input needs room for a few characters only; it grows on its own row
   when it has to wrap, so no blank line is left under the chips. */
.ui-chip-flow:has(.ui-chip) .ui-chip-text { min-width: 4ch; flex-basis: 4ch; }
.ui-chip-text::placeholder { color: var(--fg-3); opacity: 1; }
.ui-chip {
  display: inline-flex;
  max-width: 100%;
  height: 1.875rem;
  align-items: center;
  gap: 0.375rem;
  padding: 0 0.25rem 0 0.625rem;
  border-radius: 999px;
  background: rgb(from var(--fg) r g b / 0.08);
  color: var(--fg);
  font: var(--fw-label) 0.8125rem/1 var(--font-ui, "Geist Mono", ui-monospace, monospace);
  letter-spacing: -0.01em;
  transform-origin: left center;
  transition: background-color 120ms ease;
}
.ui-chip > svg:first-child { width: 0.875rem; height: 0.875rem; flex: none; fill: currentColor; color: var(--fg-2); }
.ui-chip > svg.is-line:first-child { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.ui-chip-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-chip-remove { display: grid; width: 1.375rem; height: 1.375rem; flex: none; place-items: center; padding: 0; border: 0; border-radius: 999px; background: transparent; color: var(--fg-3); cursor: pointer; }
.ui-chip-remove svg { width: 0.75rem; height: 0.75rem; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
@media (hover: hover) and (pointer: fine) { .ui-chip-remove:hover { background: rgb(from var(--fg) r g b / 0.1); color: var(--fg); } }
@media (pointer: coarse) { .ui-chip { height: 2.125rem; font-size: 0.9375rem; } .ui-chip-remove { width: 1.75rem; height: 1.75rem; } }
/* Pill: the one container for floating capsule controls (the top-bar capsule, icon buttons, the
   sidebar toggle, session tabs). Instances set only their size and content layout; the material,
   hairline, shadow, radius and press feedback live here so they can never drift apart. */
.ui-pill {
  appearance: none; display: inline-flex; align-items: center; justify-content: center; height: var(--pill-h, 2.375rem);
  border: 1px solid var(--overlay-line) !important; border-radius: 999px; background: var(--overlay); box-shadow: var(--shadow);
  -webkit-backdrop-filter: blur(var(--ui-overlay-blur)) saturate(var(--ui-overlay-saturation)); backdrop-filter: blur(var(--ui-overlay-blur)) saturate(var(--ui-overlay-saturation));
  transition: transform 140ms var(--ease-out, cubic-bezier(0.23, 1, 0.32, 1));
}
.ui-pill:active:not(:disabled) { transform: scale(var(--ui-press-scale)); }
.ui-pill-icon { width: var(--pill-h, 2.375rem); padding: 0; color: var(--fg-2); }
.ui-pill-icon svg { display: block; fill: none; stroke: currentColor; stroke-width: 1.6; }
@media (hover: hover) and (pointer: fine) { .ui-pill:hover:not(:disabled) { background: var(--overlay-solid); } }
/* Reduced motion: a pill never moves. This sheet loads after the app's, so the base transition above beats an instance's own
   `transition: none` (the sidebar toggle's, in style.css) at equal specificity; it is switched off here, where it is set. */
@media (prefers-reduced-motion: reduce) { .ui-pill { transition: none; } .ui-pill:active:not(:disabled) { transform: none; } }

/* Window: see the window card block above; terminals draw into a block canvas. */
.ui-window canvas { display: block; }

/* Panel: the same material for large floating surfaces such as the sidebar; only the radius differs. */
.ui-panel { border-radius: var(--r-lg, 1.125rem); }

/* Window resize handles (window-resize.ts): invisible hit strips on the card's edges for a
   mouse; a finger gets four corner grips only after a press-and-hold on the header. */
.ui-window-resize { position: absolute; z-index: 5; touch-action: none; }
.ui-window-resize[data-edge="n"], .ui-window-resize[data-edge="s"] { left: 1.125rem; right: 1.125rem; height: 0.375rem; }
.ui-window-resize[data-edge="e"], .ui-window-resize[data-edge="w"] { top: 1.125rem; bottom: 1.125rem; width: 0.375rem; }
.ui-window-resize[data-edge="n"] { top: 0; cursor: ns-resize; }
.ui-window-resize[data-edge="s"] { bottom: 0; cursor: ns-resize; }
.ui-window-resize[data-edge="e"] { right: 0; cursor: ew-resize; }
.ui-window-resize[data-edge="w"] { left: 0; cursor: ew-resize; }
.ui-window-resize[data-edge$="e"][data-edge^="n"], .ui-window-resize[data-edge$="w"][data-edge^="s"] { cursor: nesw-resize; }
.ui-window-resize[data-edge$="w"][data-edge^="n"], .ui-window-resize[data-edge$="e"][data-edge^="s"] { cursor: nwse-resize; }
.ui-window-resize[data-edge="ne"], .ui-window-resize[data-edge="nw"], .ui-window-resize[data-edge="se"], .ui-window-resize[data-edge="sw"] { width: 1.125rem; height: 1.125rem; }
.ui-window-resize[data-edge="ne"] { top: 0; right: 0; }
.ui-window-resize[data-edge="nw"] { top: 0; left: 0; }
.ui-window-resize[data-edge="se"] { bottom: 0; right: 0; }
.ui-window-resize[data-edge="sw"] { bottom: 0; left: 0; }
@media (pointer: coarse) { .ui-window-resize { display: none; } }

/* Touch mode: an accent outline and four corner brackets that hug the card's rounded corners,
   each with a 44 px hit area. Edges stay inert so a finger cannot resize by accident. */
.ui-window[data-resize="touch"] { border-color: var(--accent); box-shadow: var(--shadow), 0 0 0 1px var(--accent); }
.ui-window[data-resize="touch"] .ui-window-resize { display: block; }
.ui-window[data-resize="touch"] .ui-window-resize[data-edge]:not([data-edge="ne"], [data-edge="nw"], [data-edge="se"], [data-edge="sw"]) { display: none; }
.ui-window[data-resize="touch"] .ui-window-resize[data-edge="ne"], .ui-window[data-resize="touch"] .ui-window-resize[data-edge="nw"],
.ui-window[data-resize="touch"] .ui-window-resize[data-edge="se"], .ui-window[data-resize="touch"] .ui-window-resize[data-edge="sw"] {
  width: 2.75rem; height: 2.75rem; cursor: auto;
  animation: ui-grip-in 220ms var(--ease-out, cubic-bezier(0.23, 1, 0.32, 1)) both;
}
.ui-window[data-resize="touch"] .ui-window-resize::after {
  content: ""; position: absolute; width: 1.375rem; height: 1.375rem; border: 0 solid var(--accent);
  transition: transform 140ms var(--ease-out, ease-out);
}
.ui-window[data-resize="touch"] .ui-window-resize:active::after { transform: scale(1.12); }
.ui-window[data-resize="touch"] .ui-window-resize[data-edge="ne"]::after { top: 0.375rem; right: 0.375rem; border-width: 0.1875rem 0.1875rem 0 0; border-top-right-radius: 0.875rem; }
.ui-window[data-resize="touch"] .ui-window-resize[data-edge="nw"]::after { top: 0.375rem; left: 0.375rem; border-width: 0.1875rem 0 0 0.1875rem; border-top-left-radius: 0.875rem; }
.ui-window[data-resize="touch"] .ui-window-resize[data-edge="se"]::after { bottom: 0.375rem; right: 0.375rem; border-width: 0 0.1875rem 0.1875rem 0; border-bottom-right-radius: 0.875rem; }
.ui-window[data-resize="touch"] .ui-window-resize[data-edge="sw"]::after { bottom: 0.375rem; left: 0.375rem; border-width: 0 0 0.1875rem 0.1875rem; border-bottom-left-radius: 0.875rem; }
@keyframes ui-grip-in { from { opacity: 0; } to { opacity: 1; } }

/* Renaming in place (inline-rename.ts): a flat field in the name's own type, tinted like a hovered row. No ring from
   mouse or touch (TASTE §1); the tint and the caret say where you are. Surfaces add only size and position. */
.ui-inline-rename {
  box-sizing: border-box; min-width: 0; height: 22px; padding: 0 6px; border: 0; border-radius: 6px; outline: 0;
  background: var(--hover); color: var(--fg); font: var(--ui-type-button); letter-spacing: var(--ui-tracking);
  -webkit-user-select: text; user-select: text; text-shadow: none;
}
/* 16 px on touch so iOS never zooms into the field (the one type scale, TASTE §2). */
@media (pointer: coarse) { .ui-inline-rename { height: 32px; font-size: var(--text-input, 16px); } }

/* Sidebar footer (sidebar-foot.ts): the person's pill and the settings pill share one height, so
   their centres sit on one line. Pills are the shared `.ui-pill`; only layout lives here. */
.ui-sidebar-foot { --pill-h: 2.5rem; display: flex; flex: none; align-items: center; justify-content: space-between; gap: 0.5rem; margin-top: auto; padding: 0.5625rem 0 0; box-shadow: 0 -1px 0 var(--line); }
.ui-sidebar-foot[data-variant="b"] { --pill-h: 2rem; }
@media (pointer: coarse) { .ui-sidebar-foot, .ui-sidebar-foot[data-variant="b"] { --pill-h: 2.75rem; } }
.ui-sidebar-foot .ui-pill { cursor: pointer; font: var(--ui-type-label); letter-spacing: var(--ui-tracking); line-height: 1; white-space: nowrap; transition: transform 140ms var(--ease-out, ease-out), background-color 140ms ease, box-shadow 160ms ease; }
@media (hover: hover) and (pointer: fine) { .ui-sidebar-foot .ui-pill:hover:not(:active) { box-shadow: var(--shadow), 0 2px 0.5rem -2px rgb(0 0 0 / 0.18); } }
.ui-sidebar-user { flex: 0 1 auto; min-width: 0; max-width: calc(100% - var(--pill-h) - 0.5rem); justify-content: flex-start; gap: 0.625rem; padding: 0 0.875rem 0 0.3125rem; text-align: left; }
.ui-sidebar-foot[data-variant="b"] .ui-sidebar-user { padding-right: 0.75rem; }
.ui-sidebar-avatar {
  display: grid; flex: none; place-items: center; width: calc(var(--pill-h) - 0.625rem); height: calc(var(--pill-h) - 0.625rem); border-radius: 50%; color: #fff;
  background: linear-gradient(145deg, oklch(from var(--accent) calc(l + 0.06) c calc(h + var(--ui-avatar-hue, 0))), oklch(from var(--accent) calc(l - 0.12) c calc(h + var(--ui-avatar-hue, 0) + 28)));
  font: 600 0.75rem/1 var(--font-ui, "Geist Mono", ui-monospace, monospace); text-shadow: 0 1px 1px rgb(0 0 0 / 0.25);
}
.ui-sidebar-foot[data-variant="b"] .ui-sidebar-avatar { box-shadow: 0 0 0 1.5px color-mix(in oklab, var(--accent) 40%, transparent); }
@media (pointer: coarse) { .ui-sidebar-avatar { font-size: 0.875rem; } .ui-sidebar-foot .ui-pill { font-size: var(--text-secondary); } }
.ui-sidebar-names { display: grid; min-width: 0; gap: 0.1875rem; }
.ui-sidebar-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--fg); font-weight: var(--fw-label); }
.ui-sidebar-secondary { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--ui-text-secondary); font-size: 0.6875rem; }
@media (pointer: coarse) { .ui-sidebar-secondary { font-size: 0.75rem; } }
.ui-sidebar-settings svg { stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }

/* Window lock (lock-toggle.ts, window-card.ts): the padlock glyph itself, no chip. It is centred on
   the window's top frame line: the shackle rises above the line and the body hangs just below it, so
   it looks anchored on the frame. Invisible until the pointer is near the top edge, then 35 %;
   locked, it stays at 0.9 with an accent tint and the window's hairline is stronger. The button is a
   44 px invisible hit area. On touch there is no frame lock: locking lives in the toolbar and the
   palette. */
.ui-window-lock {
  position: absolute; z-index: 6; top: -1.375rem; left: 50%; width: 2.75rem; height: 2.75rem; margin-left: -1.375rem;
  display: grid; place-items: center; padding: 0; border: 0; border-radius: 0.625rem; background: none; color: var(--fg);
  opacity: 0; pointer-events: none; cursor: pointer; transition: opacity 160ms ease-out, color 160ms ease-out;
}
.ui-window-lock svg { width: 1rem; height: 1rem; transform: translateY(0.5px); fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; overflow: visible; }
.ui-window[data-lock-near] .ui-window-lock { opacity: 0.35; pointer-events: auto; }
.ui-window[data-locked] .ui-window-lock { opacity: 0.9; pointer-events: auto; color: var(--accent); }
.ui-window-lock:focus-visible { opacity: 0.8; }
@media (hover: hover) and (pointer: fine) { .ui-window .ui-window-lock:hover { opacity: 0.8; } }
.ui-window-lock:active svg { transform: translateY(0.5px) scale(var(--ui-press-scale-small)); }
.ui-window[data-locked] { border-color: color-mix(in oklab, var(--accent) 78%, var(--overlay-line)); box-shadow: var(--shadow), 0 0 0 1px color-mix(in oklab, var(--accent) 28%, transparent); }
@media (pointer: coarse) { .ui-window-lock { display: none; } }
@media (prefers-reduced-motion: reduce) { .ui-window-lock { transition: opacity 160ms ease; } }
.ui-window[data-status="none"] .ui-window-lock { display: none; }

/* Shortcuts view (shortcuts-view.ts): the capsule's morph target. The material is the shared
   overlay surface; only the layout lives here. */
.ui-shortcuts { position: absolute; z-index: 7; display: flex; flex-direction: column; min-height: 0; border-radius: var(--r-lg, 1.125rem); color: var(--fg); outline: none; }
.ui-shortcuts-head { display: flex; flex: none; align-items: center; justify-content: space-between; gap: 0.75rem; height: var(--pill-h, 2.375rem); padding: 0 0.5rem 0 0.875rem; font: var(--ui-type-name); letter-spacing: var(--ui-tracking); }
.ui-shortcuts-body { min-height: 0; overflow: auto; overscroll-behavior: contain; padding: 0.25rem 0.875rem 0.875rem; }
.ui-shortcuts-section + .ui-shortcuts-section { margin-top: 0.75rem; }
.ui-shortcuts-title { margin: 0; padding: var(--ui-heading-pad); color: var(--ui-text-secondary); font: var(--ui-type-eyebrow); letter-spacing: 0.02em; text-transform: uppercase; }
.ui-shortcuts-list { display: grid; margin: 0; padding: 0; list-style: none; }
.ui-shortcuts-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; min-height: var(--row, 1.75rem); border-bottom: 1px solid var(--line); }
.ui-shortcuts-row:last-child { border-bottom: 0; }
.ui-shortcuts-label { min-width: 0; color: var(--fg); font: var(--ui-type-label); letter-spacing: var(--ui-tracking); }
.ui-shortcuts-keys { display: inline-flex; flex: none; gap: 0.25rem; }
.ui-shortcuts kbd { min-width: 1.375rem; padding: 0.25rem 0.375rem; border-radius: 0.375rem; background: var(--hover); color: var(--fg-2); font: 500 var(--text-caption)/1 var(--font-ui, "Geist Mono", ui-monospace, monospace); text-align: center; }
/* An alternate binding: the same keycap, quieter, so the primary reads first (TASTE: hierarchy by weight and tone). */
.ui-shortcuts kbd.ui-shortcuts-alt { color: var(--fg-3); opacity: 0.7; }
@media (pointer: coarse) { .ui-shortcuts-label { font-size: var(--text-body); } .ui-shortcuts-head { padding-right: 0.875rem; } }

/* Settings overlay (settings-overlay.ts): one centred card in the shared overlay material. */
.ui-settings { position: absolute; z-index: 8; top: 50%; left: 50%; display: flex; flex-direction: column; width: min(35rem, calc(100% - 1.5rem)); max-height: min(32.5rem, calc(100% - 1.5rem)); margin: 0; translate: -50% -50%; border-radius: var(--r-lg, 1.125rem); color: var(--fg); outline: none; }
.ui-settings-head { display: flex; flex: none; align-items: center; justify-content: space-between; height: 3.25rem; padding: 0 0.5rem 0 1.125rem; }
.ui-settings-title { font: var(--ui-type-name); letter-spacing: var(--ui-tracking); }
.ui-settings-nav-row:active { transform: scale(var(--ui-press-scale)); }
@media (hover: hover) and (pointer: fine) { .ui-settings-nav-row:hover { background: var(--hover); } }
.ui-settings-main { display: flex; min-height: 0; flex: 1; border-top: 1px solid var(--line); }
.ui-settings-nav { display: flex; flex: none; flex-direction: column; gap: 2px; width: 9.375rem; padding: 0.5rem; border-right: 1px solid var(--line); }
.ui-settings-nav-row { display: flex; align-items: center; gap: 0.5rem; height: var(--ui-row-h, 2rem); padding: 0 0.5rem; border: 0; border-radius: 0.5rem; background: none; color: var(--fg-2); font: var(--ui-type-label); letter-spacing: var(--ui-tracking); text-align: left; cursor: pointer; transition: background-color 140ms ease, transform 140ms var(--ease-out, ease-out); }
.ui-settings-nav-row[aria-current="true"] { background: var(--hover); color: var(--fg); }
.ui-settings-nav-row svg { width: var(--ui-icon); height: var(--ui-icon); flex: none; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.ui-settings-pane { min-width: 0; flex: 1; overflow: auto; padding: 0.375rem 1.125rem 0.875rem; overscroll-behavior: contain; }
@media (max-width: 520px) {
  .ui-settings-main { flex-direction: column; }
  .ui-settings-nav { width: auto; flex-direction: row; border-right: 0; border-bottom: 1px solid var(--line); }
}

/* ---- connection indicator (src/connection-indicator.ts) ------------------------------------
   One 16 px box; each glyph is its own layer. The shown glyph is chosen by transitions on
   opacity and transform only, so a state change retargets from the current frame. Looping
   animations are paused (never removed) when hidden, so they cannot jump. */
.ui-conn { position: relative; display: inline-block; flex: none; width: var(--ui-conn-size, 1rem); height: var(--ui-conn-size, 1rem); vertical-align: middle; cursor: default; -webkit-tap-highlight-color: transparent; }
.ui-conn[hidden] { display: none; }
@media (pointer: coarse) { .ui-conn::after { content: ""; position: absolute; inset: -0.5rem; } }
/* Keys that were typed while the link was down and are kept to be sent (connection-indicator.ts `queued`): one small amber key at the glyph's corner. */
.ui-conn-q { position: absolute; right: -0.1875rem; top: -0.1875rem; display: grid; place-items: center; width: 0.5625rem; height: 0.5625rem; border-radius: 50%; background: var(--ui-conn-ground, var(--bg)); opacity: 0; transform: scale(0.4); transition: opacity 160ms ease-out, transform 220ms cubic-bezier(0.23, 1, 0.32, 1); pointer-events: none; }
.ui-conn-q svg { width: 0.5625rem; height: 0.5625rem; fill: none; stroke: var(--warn); stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.ui-conn[data-queued] .ui-conn-q { opacity: 1; transform: none; }
.ui-conn-g { position: absolute; inset: 0; display: grid; place-items: center; opacity: 0; transform: scale(0.6); pointer-events: none;
  transition: opacity 160ms ease-out, transform 240ms var(--ease-out, cubic-bezier(0.23, 1, 0.32, 1)); }
.ui-conn-g svg, .ui-conn-pulse, .ui-conn-spin { grid-area: 1 / 1; width: 100%; height: 100%; overflow: visible; fill: none; }
/* Glyph parts opt in to a stroke; an inherited page-wide svg stroke must not outline the filled ones. */
.ui-conn-g svg { stroke: none; }
.ui-conn-g svg * { stroke: none; }
.ui-conn[data-state="connected"] .ui-conn-g-connected,
.ui-conn[data-state="slow"] .ui-conn-g-slow,
.ui-conn[data-state="connecting"] .ui-conn-g-reconnecting,
.ui-conn[data-state="reconnecting"] .ui-conn-g-reconnecting,
.ui-conn[data-state="resumed"] .ui-conn-g-resumed,
.ui-conn[data-state="approximate"] .ui-conn-g-approximate,
.ui-conn[data-state="offline"] .ui-conn-g-offline { opacity: 1; transform: none; }

.ui-conn-g-connected .core { fill: var(--ok); }
.ui-conn-g-connected .halo { fill: color-mix(in oklab, var(--ok) 20%, transparent); }
.ui-conn-g-slow .core { fill: var(--warn); }
.ui-conn-g-slow .halo { fill: color-mix(in oklab, var(--warn) 22%, transparent); }
.ui-conn .ui-conn-g-slow .ring { stroke: var(--warn); stroke-width: 1.2; }
.ui-conn-pulse { position: absolute; inset: 0; transform-origin: 50% 50%; opacity: 0; animation: ui-conn-pulse 1700ms cubic-bezier(0.23, 1, 0.32, 1) 3; animation-play-state: paused; }
/* The breath restarts by switching to an identical animation under another name (`data-breath`), which needs no style flush. */
.ui-conn[data-breath="1"] .ui-conn-pulse { animation-name: ui-conn-pulse-b; }
.ui-conn[data-state="slow"] .ui-conn-pulse { animation-play-state: running; will-change: transform, opacity; }
@keyframes ui-conn-pulse { 0% { opacity: 0.7; transform: scale(1); } 70%, 100% { opacity: 0; transform: scale(2.1); } }
@keyframes ui-conn-pulse-b { 0% { opacity: 0.7; transform: scale(1); } 70%, 100% { opacity: 0; transform: scale(2.1); } }

.ui-conn .ui-conn-g-reconnecting .track { stroke: var(--fg-3); stroke-width: 1.4; opacity: 0.4; }
.ui-conn .ui-conn-g-reconnecting .arc { stroke: var(--fg-2); stroke-width: 1.7; stroke-linecap: round; stroke-dasharray: 28 72; }
.ui-conn-spin { position: absolute; inset: 0; animation: ui-conn-turn 900ms linear infinite; animation-play-state: paused; }
.ui-conn[data-state="reconnecting"] .ui-conn-spin, .ui-conn[data-state="connecting"] .ui-conn-spin { animation-play-state: running; }
@keyframes ui-conn-turn { to { transform: rotate(1turn); } }

.ui-conn-g-resumed .disc { fill: color-mix(in oklab, var(--ok) 22%, transparent); }
.ui-conn .ui-conn-g-resumed .tick { stroke: var(--ok); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* The tick arrives with a little overshoot, because something just happened. */
.ui-conn[data-state="resumed"] .ui-conn-g-resumed { transition-timing-function: ease-out, cubic-bezier(0.23, 1, 0.32, 1); }

.ui-conn .ui-conn-g-approximate .frame { stroke: var(--fg-2); stroke-width: 1.4; }
.ui-conn-g-approximate .fill { fill: var(--fg-2); opacity: 0.55; }
.ui-conn .ui-conn-g-approximate .foot { stroke: var(--fg-2); stroke-width: 1.4; stroke-linecap: round; }

.ui-conn .ui-conn-g-offline .ring { stroke: var(--fg-2); stroke-width: 1.4; opacity: 0.8; }
.ui-conn .ui-conn-g-offline .slash { stroke: var(--fg-2); stroke-width: 1.4; stroke-linecap: round; opacity: 0.8; }

/* Reduced motion: glyphs cross-fade; nothing moves or loops. The reconnecting arc stays as a static mark. */
@media (prefers-reduced-motion: reduce) {
  .ui-conn-g { transform: none; transition: opacity 200ms ease; }
  .ui-conn-q { transform: none; transition: opacity 200ms ease; }
  .ui-conn-pulse, .ui-conn-spin { animation: none; }
  .ui-conn-pulse { display: none; }
}

/* Settings, Toolbar (toolbar-settings.ts): rows of controls with a grip, an icon, a name and a switch. */
.ui-toolbar-settings { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.875rem; }
.ui-ts-title { margin: 0; padding: var(--ui-heading-pad); color: var(--ui-text-secondary); font: var(--ui-type-eyebrow); letter-spacing: 0.02em; text-transform: uppercase; }
.ui-ts-list { display: grid; margin: 0; padding: 0; list-style: none; }
.ui-ts-item { position: relative; display: flex; align-items: center; gap: 0.625rem; min-height: var(--ui-row-h, 2rem); border-bottom: 1px solid var(--line); background: transparent; }
.ui-ts-item:last-child { border-bottom: 0; }
.ui-ts-item[data-dragging] { z-index: 2; background: var(--overlay-solid); border-radius: 0.625rem; box-shadow: var(--shadow); }
.ui-ts-grip { display: grid; flex: none; place-items: center; width: var(--ui-control-h, 1.875rem); height: var(--ui-control-h, 1.875rem); padding: 0; border: 0; border-radius: 0.5rem; background: none; color: var(--fg-3); cursor: grab; touch-action: none; }
.ui-ts-grip svg { width: var(--ui-icon, 1rem); height: var(--ui-icon, 1rem); fill: none; stroke: currentColor; stroke-linecap: round; }
.ui-ts-item[data-dragging] .ui-ts-grip { cursor: grabbing; }
.ui-ts-icon { display: grid; flex: none; color: var(--fg-2); }
.ui-ts-icon svg { width: var(--ui-icon, 1rem); height: var(--ui-icon, 1rem); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.ui-ts-label { flex: 1; min-width: 0; color: var(--fg); font: var(--ui-type-label); line-height: normal; letter-spacing: var(--ui-tracking); } /* the label's line box is the font's own height, so it shares the icon's centre line (#282) */
.ui-ts-reset { justify-self: start; min-height: var(--ui-control-h, 1.875rem); padding: 0 0.75rem; border: 1px solid var(--overlay-line); border-radius: 999px; background: none; color: var(--fg); font: var(--ui-type-button); letter-spacing: var(--ui-tracking); cursor: pointer; transition: background-color 140ms ease, transform 140ms var(--ease-out, ease-out); }
.ui-ts-reset:active { transform: scale(var(--ui-press-scale, 0.97)); }
@media (hover: hover) and (pointer: fine) { .ui-ts-reset:hover, .ui-ts-grip:hover { background: var(--hover); } }

/* Settings, Shortcuts (shortcuts-settings.ts): the registry's rows; a row is a button that listens for the new keys. */
.ui-shortcuts-settings { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.75rem; }
.ui-ss-list { display: grid; margin: 0; padding: 0; list-style: none; }
.ui-ss-row { position: relative; display: flex; flex-wrap: wrap; align-items: center; border-bottom: 1px solid var(--line); }
.ui-ss-row:last-child { border-bottom: 0; }
.ui-ss-main { display: flex; flex: 1; align-items: center; justify-content: space-between; gap: 1rem; min-height: var(--row, 1.75rem); padding: 0; border: 0; background: none; color: var(--fg); font: var(--ui-type-label); letter-spacing: var(--ui-tracking); text-align: left; cursor: pointer; }
.ui-ss-main:disabled { cursor: default; color: var(--ui-text-secondary); }
.ui-ss-keys { position: relative; display: inline-flex; flex: none; gap: 0.25rem; }
/* A changed shortcut's mark (shortcuts-settings.ts): a dot on the first cap's top-left corner. Out of the flow, so the caps keep the shared right edge. */
.ui-ss-dot { position: absolute; top: -0.125rem; left: -0.125rem; z-index: 1; width: 0.375rem; height: 0.375rem; border-radius: 50%; background: var(--accent); pointer-events: none; }
.ui-ss-row[data-reset-all] .ui-ss-label { color: var(--danger); }
/* "Reset all" asks in its own row (row-confirm.ts): its words crossfade into the question, like the sidebar's rows above. */
.ui-ss-row[data-reset-all] > .ui-ss-main > * { transition: opacity 150ms var(--ease-out, ease-out); }
.ui-ss-row[data-asking] > .ui-ss-main > * { opacity: 0; }
@media (prefers-reduced-motion: reduce) { .ui-ss-row[data-reset-all] > .ui-ss-main > * { transition: none; } }
.ui-ss-main { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
.ui-ss-keys[data-capturing] { color: var(--accent); }
.ui-ss-keys kbd { min-width: 1.375rem; padding: 0.25rem 0.375rem; border-radius: 0.375rem; background: var(--hover); color: var(--fg-2); font: var(--fw-label, 500) var(--text-caption, 0.6875rem)/1 var(--font-ui); text-align: center; }
.ui-ss-warn { display: flex; flex: 1 0 100%; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem; padding: 0.25rem 0 0.5rem; color: var(--warn, var(--fg-2)); font: var(--ui-type-label); letter-spacing: var(--ui-tracking); }
.ui-ss-actions { display: inline-flex; gap: 0.375rem; }
.ui-ss-actions button { min-height: var(--ui-control-h, 1.875rem); padding: 0 0.75rem; border: 1px solid var(--overlay-line); border-radius: 999px; background: none; color: var(--fg); font: var(--ui-type-button); cursor: pointer; }
@media (hover: hover) and (pointer: fine) { .ui-ss-actions button:hover, .ui-ss-main:not(:disabled):hover { background: var(--hover); } }
.ui-ts-source { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.625rem; }
.ui-ts-tag { padding: 0.1875rem 0.5625rem; border-radius: 999px; background: var(--hover); color: var(--ui-text-secondary); font: var(--ui-type-eyebrow); letter-spacing: 0.02em; text-transform: uppercase; }
.ui-ts-tag[data-device="true"] { background: var(--accent-soft); color: var(--accent); }
.ui-ts-tag, .ui-ts-source .ui-ts-reset, .ui-ss-actions button { white-space: nowrap; }
.keyedit-list::before { white-space: nowrap; }

/* Canvas backdrop (canvas-backdrop.ts): the gradient is fixed to the screen and visible everywhere; the dots are light marks on it and move with the camera. */
.ui-canvas-backdrop {
  --ui-dot-size: 1.4px; --ui-dot-gap: 22px; --ui-dot-opacity: var(--dot-opacity, 0.35); --ui-gradient-opacity: var(--gradient-opacity, 0.5);
  --ui-dot-color: var(--dot-color, color-mix(in oklab, var(--mesh-3) 40%, white));
  position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none;
}
/* The three backgrounds (Settings, Appearance). Soft is the defaults above: the theme's gentle gradient. Plain is one
   solid colour with the dots in a quiet tint of the text colour. Mesh is the theme's full mesh gradient (the same
   image the rest of the app paints), fixed to the screen, with the dots moving over it. */
:root[data-bg="none"] .ui-canvas-backdrop { --ui-gradient-opacity: 0; --ui-dot-opacity: 0.2; --ui-dot-color: color-mix(in srgb, var(--fg) 60%, var(--bg)); }
:root[data-bg="mesh"] .ui-canvas-backdrop { --ui-gradient-opacity: 1; --ui-dot-fill: var(--mesh-image, linear-gradient(115deg, var(--mesh-1), var(--mesh-2) 35%, var(--mesh-3) 70%, var(--mesh-4))) center / cover no-repeat; }
.ui-canvas-backdrop-fill { position: absolute; inset: 0; opacity: var(--ui-gradient-opacity); background: var(--ui-dot-fill, linear-gradient(115deg, var(--mesh-1), var(--mesh-2) 35%, var(--mesh-3) 70%, var(--mesh-4))); }
.ui-canvas-backdrop-layer[hidden] { display: none; }
.ui-canvas-backdrop-layer {
  position: absolute; left: 0; top: 0; will-change: transform, opacity;
  background-image: radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--ui-dot-color) calc(var(--ui-dot-opacity) * 100%), transparent) calc(var(--ui-dot-size) / 2), transparent calc(var(--ui-dot-size) / 2 + 0.7px));
}

/* Settings windows (settings-panels.ts): choice rows with a tick, device rows, headings; every colour, size and radius is a token. */
/* One column that is the pane's width, never wider: a window keeps its width when the UI size grows (#283), and a row that cannot wrap (a theme and its "dark") must not widen the whole column and push every section's right edge out of the window. */
.ui-sp { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.875rem; }
.ui-sp-list { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2px; }
.ui-sp-row { display: flex; align-items: center; gap: 0.625rem; width: 100%; min-height: var(--ui-row-h, 2rem); padding: 0 0.5rem; border: 0; border-radius: 0.5rem; background: none; color: var(--fg); font: var(--ui-type-label); letter-spacing: var(--ui-tracking); text-align: left; cursor: pointer; transition: background-color 140ms ease, transform 140ms var(--ease-out, ease-out); }
.ui-sp-row:active { transform: scale(var(--ui-press-scale, 0.97)); }
@media (hover: hover) and (pointer: fine) { .ui-sp-row:hover { background: var(--hover); } }
.ui-sp-swatch, .ui-sp-slot { flex: none; width: 1.125rem; height: 1.125rem; }
.ui-sp-swatch { position: relative; border-radius: 50%; border: 1px solid var(--overlay-line); }
.ui-sp-swatch::after { content: ""; position: absolute; right: 2px; bottom: 2px; width: 0.375rem; height: 0.375rem; border-radius: 50%; background: var(--dot); }
/* An option row (choice list): the name is left, the description is right-aligned at the end of the text column, and the
   tick keeps its own fixed column after it (always laid out, only its opacity changes), so every description of a list
   ends at one x whether or not its row is selected. The name is one unbreakable line and is never truncated. When the
   list's widest row does not fit (decided per list in fitHints) the list is `data-stacked` and every description sits
   under its name as a left-aligned subtitle, balanced and allowed two lines. The tick stays centred on the row. */
.ui-sp-text { display: flex; flex: 1; align-items: baseline; justify-content: space-between; column-gap: 0.625rem; min-width: 0; padding: 0.3125rem 0; }
.ui-sp-text .ui-sp-label { flex: none; white-space: nowrap; }
.ui-sp-text .ui-sp-hint { flex: none; margin-left: auto; text-align: right; white-space: nowrap; color: var(--ui-text-secondary); }
.ui-sp-text .ui-sp-hint:empty { display: none; }
[data-stacked] > .ui-sp-row .ui-sp-text { flex-direction: column; align-items: flex-start; justify-content: flex-start; row-gap: 1px; }
[data-stacked] > .ui-sp-row .ui-sp-hint { margin-left: 0; text-align: left; white-space: normal; text-wrap: balance; max-width: 100%; }
/* Every settings row (option, switch, status) starts at its heading's left edge: no indent, so the swatch, the name or
   the first letter sits exactly under the heading's first letter. The hover fill still has room around the content: it
   is a pseudo-element that bleeds 0.5rem to each side into the pane's own padding (the row's box, and so every
   measurement of it, stays on the heading's edge). `--ui-sp-end` is how far the row's last mark stops short of the
   far-right edge: nothing, except in a window with option lists (`data-ticks`), where it is the tick's column plus its
   gap, so a switch, a button or a readout ends where the option descriptions end. */
.ui-sp { --ui-sp-end: 0px; }
.ui-sp[data-ticks] { --ui-sp-end: calc(var(--ui-sp-tick, 1.125rem) + 0.625rem); }
.ui-sp .ui-sp-row { position: relative; isolation: isolate; padding: 0; }
.ui-sp .ui-sp-row:is(.ui-sp-switch, .ui-sp-status) { padding-right: var(--ui-sp-end); }
.ui-sp .ui-sp-row::before { content: ""; position: absolute; inset: 0 -0.5rem; z-index: -1; border-radius: 0.5rem; pointer-events: none; transition: background-color 140ms ease; }
@media (hover: hover) and (pointer: fine) {
  .ui-sp .ui-sp-row:hover { background: none; }
  .ui-sp .ui-sp-row:hover::before { background: var(--hover); }
  .ui-sp .ui-sp-status:hover::before { background: none; }
}
/* A switch's visible track is its ::before (content-box: 1px in, 2.875rem wide, 1px border either side), a hair wider than
   the 3rem box that holds it. The row's right edge is the track's, not the box's: the box stops short by the difference. */
.ui-sp .ui-switch, .ui-ts-item .ui-switch { margin-right: calc(3px - 0.125rem); }
.ui-sp[data-ticks] .ui-slider { margin-right: var(--ui-sp-end); }
/* Icons are aligned by what you see: a glyph sits on the edge, and its hit box reaches past it into the pane's padding. */
.ui-sp-kind { margin-left: calc((var(--ui-icon, 1rem) - 1.75rem) / 2); }
.ui-sp-actions .ui-window-action, .ui-sp-status .ui-window-action { width: var(--ui-control-h, 1.875rem); height: var(--ui-control-h, 1.875rem); margin-right: calc((var(--ui-icon, 1rem) - var(--ui-control-h, 1.875rem)) / 2); }
.ui-sp-actions .ui-window-action::after, .ui-sp-status .ui-window-action::after { content: none; }
.ui-ts-grip { margin-left: calc((var(--ui-icon, 1rem) - var(--ui-control-h, 1.875rem)) / 2); }

.ui-sp-hint { color: var(--ui-text-secondary); }
/* A destructive row (remove, sign out) names its action in the danger colour; the hint under it stays quiet. */
.ui-sp-row[data-tone="danger"] .ui-sp-label { color: var(--danger); }
.ui-sp-tick { width: var(--ui-sp-tick, 1.125rem); height: var(--ui-sp-tick, 1.125rem); flex: none; fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; opacity: 0; transform: scale(0.6); transition: opacity 150ms ease, transform 180ms var(--ease-out, ease-out); }
.ui-sp-row[data-current] .ui-sp-tick { opacity: 1; transform: none; }
.ui-sp-devices { display: grid; margin: 0; padding: 0; list-style: none; }
.ui-sp-device { display: flex; align-items: center; gap: 0.625rem; min-height: 3.25rem; border-bottom: 1px solid var(--line); }
.ui-sp-device:last-child { border-bottom: 0; }
.ui-sp-kind { position: relative; display: grid; flex: none; place-items: center; width: 1.75rem; height: 1.75rem; color: var(--fg-2); }
.ui-sp-kind svg { width: var(--ui-icon, 1rem); height: var(--ui-icon, 1rem); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.ui-sp-dot { position: absolute; right: 1px; bottom: 1px; width: 0.4375rem; height: 0.4375rem; border-radius: 50%; background: var(--ok); box-shadow: 0 0 0 2px var(--bg); }
.ui-sp-dot[data-online="false"] { background: var(--fg-3); }
/* A static row (a fact and its action): no press scale or hover fill, because the row itself does nothing. The action fades
   instead of vanishing so the words keep their place. */
.ui-sp-status { cursor: default; min-height: var(--ui-row-h, 2rem); }
.ui-sp-status:active { transform: none; }
@media (hover: hover) and (pointer: fine) { .ui-sp-status:hover { background: none; } }
.ui-sp-status-action { flex: none; transition: opacity 160ms ease, background-color 140ms ease, transform 140ms var(--ease-out, ease-out); }
.ui-sp-status-action[data-shown="false"] { opacity: 0; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .ui-sp-status-action { transition: none; } }
.ui-sp-names { display: grid; flex: 1; min-width: 0; gap: 2px; }
.ui-sp-name-row { display: flex; align-items: baseline; gap: 0.5rem; min-width: 0; }
.ui-sp-name { min-width: 0; max-width: 100%; padding: 0 2px; margin: 0 -2px; border: 0; border-radius: 0.375rem; background: none; outline: none; color: var(--fg); font: var(--ui-type-label); font-weight: var(--fw-label, 500); letter-spacing: var(--ui-tracking); }
.ui-sp-name:focus { background: var(--hover); }
.ui-sp-here { color: var(--ui-text-secondary); font: var(--ui-type-label); font-size: var(--text-secondary, 0.75rem); white-space: nowrap; }
.ui-sp-sub { color: var(--ui-text-secondary); font: var(--ui-type-label); font-size: var(--text-secondary, 0.75rem); letter-spacing: var(--ui-tracking); }
.ui-sp-actions { display: flex; flex: none; gap: 2px; }
.ui-ts-tag, .ui-ts-source .ui-ts-reset, .ui-ss-actions button { white-space: nowrap; }
.keyedit-list::before { white-space: nowrap; }

/* A chip used as a toggle (a view to share, a filter): pressed is a quiet accent fill, nothing else changes. */
.ui-chip[aria-pressed] { padding-right: 0.625rem; cursor: pointer; }
.ui-chip[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); }

/* Item states (branched.ts `state` and `mark`, and the session manager): an item is open (filled dot), detached
   (hollow dot) or exited (hollow square). A mark follows: new output is a quiet dot, a finished command a tick
   or a cross, so the exit code beside it reads "✓ 0" or "✗ 1". One drawing, used by every list of such items. */
.ui-state-dot { display: grid; flex: none; place-items: center; width: 1.125rem; }
.ui-state-dot::before { content: ""; box-sizing: border-box; width: 0.4375rem; height: 0.4375rem; border-radius: 50%; }
.ui-state-dot[data-state="open"]::before { background: var(--fg-2); }
.ui-state-dot[data-state="detached"]::before { box-shadow: inset 0 0 0 1.5px var(--fg-3); }
.ui-state-dot[data-state="exited"]::before { width: 0.375rem; height: 0.375rem; border-radius: 1.5px; box-shadow: inset 0 0 0 1.5px var(--fg-3); }
.bm-leaf[aria-current] .ui-state-dot[data-state="open"]::before { background: var(--accent); }
.ui-mark { flex: none; width: 0.75rem; height: 0.75rem; }
.ui-mark.is-new { animation: ui-mark-in 180ms var(--ease-out, ease-out); }
.ui-mark[data-mark="output"] { width: 0.375rem; height: 0.375rem; margin: 0 0.1875rem; border-radius: 50%; background: var(--accent); }
.ui-mark[data-mark="ok"], .ui-mark[data-mark="fail"] { background: currentColor; -webkit-mask: var(--ui-mark-icon) center / contain no-repeat; mask: var(--ui-mark-icon) center / contain no-repeat; }
.ui-mark[data-mark="ok"] { color: var(--ok); --ui-mark-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E"); }
/* Ended: the session is gone without an exit code to report. Quiet, not an error: a stopped square. */
.ui-mark[data-mark="ended"] { color: var(--fg-2); background: currentColor; -webkit-mask: var(--ui-mark-icon) center / contain no-repeat; mask: var(--ui-mark-icon) center / contain no-repeat; --ui-mark-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linejoin='round'%3E%3Crect x='6.5' y='6.5' width='11' height='11' rx='2.5'/%3E%3C/svg%3E"); }
.ui-mark[data-mark="fail"] { color: var(--danger); --ui-mark-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round'%3E%3Cpath d='M6.5 6.5l11 11M17.5 6.5l-11 11'/%3E%3C/svg%3E"); }
/* In a branched menu the mark sits on the right, against the note beside it. */
.bm-leaf .ui-mark { margin-left: auto; }
.bm-leaf .ui-mark[data-mark="output"] { margin-right: 0.1875rem; }
.bm-leaf .ui-mark + .bm-meta { margin-left: 0.375rem; }
@keyframes ui-mark-in { from { opacity: 0; transform: scale(0.5); } }
@media (prefers-reduced-motion: reduce) { .ui-mark.is-new { animation: none; } }
/* A long leaf name gives way to its marks and notes instead of wrapping onto a second line. */
.bm-leaf .bm-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A caption between groups inside one section: the host above its sessions. It has no branch of its own. */
.bm-subhead { display: flex; align-items: center; height: var(--row); padding: 0 0.5rem; color: var(--fg-2); font: var(--fw-label) var(--text-caption)/1.2 var(--font-mono); letter-spacing: 0; }

/* Entity chips (entity-chip.ts): a recognised thing named in text. Text-height, on the baseline, one unit when the line
   wraps, never animated. The fill is set inline from the palette tokens, so a theme switch recolours it with no script. */
.ui-entity {
  display: inline-flex; align-items: center; gap: 0.3em; box-sizing: border-box; max-width: 100%; padding: 0 0.55em 0 0.45em;
  border-radius: 999px; font-weight: 500; line-height: 1.35; white-space: nowrap; text-shadow: none; vertical-align: baseline;
  letter-spacing: 0; transition: none; animation: none;
}
.ui-entity-name { overflow: hidden; text-overflow: ellipsis; }
.ui-entity-glyph { flex: none; width: 0.9em; height: 0.9em; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.ui-entity-glyph.ui-entity-fill { fill: currentColor; stroke: none; }
.ui-entity-initial { flex: none; min-width: 0.9em; text-align: center; font-size: 0.9em; font-weight: 600; line-height: 1; }
.ui-entity-unit { white-space: nowrap; }
/* The tile (entity-chip.ts, entityTile): a rounded square with one letter, sized by the font it sits in. */
.ui-entity-tile { justify-content: center; width: 1.45em; height: 1.45em; padding: 0; border-radius: 0.38em; font-size: 0.82em; font-weight: 600; line-height: 1; letter-spacing: 0; }

/* ---- scroll bars only while scrolling (packages/ui/src/scrollbars.ts; TASTE §4) ------------------------------ */
/* Every scroll container except terminals: no bar at rest, a thin thumb while `data-scrolling` is set, which the
   helper clears 800 ms after the last scroll. The thumb is --fg-2 at 45%, and clearing it fades the colour out.
   Two engines, two property sets, because each one ignores the other's:
   - Firefox reads scrollbar-width and scrollbar-color.
   - Chromium and Safari ignore ::-webkit-scrollbar once scrollbar-width is set (Chromium 121+), so they get the
     webkit parts instead. The buttons are hidden: a classic bar draws arrows on Windows and Linux, which is chrome.
   :where() keeps the Firefox rules at zero specificity, so a surface that sets its own scrollbar-width still wins. */
/* Only on a fine pointer (desktop, laptop): touch devices already draw overlay bars that take no layout space.
   A reserved 8 px bar would move the phone's key tray and its targets. */
@media (hover: hover) and (pointer: fine) {
@supports (-moz-appearance: none) {
  :where(*:not(.terminal-host)) { scrollbar-width: thin; scrollbar-color: transparent transparent; transition: scrollbar-color 240ms cubic-bezier(0.23, 1, 0.32, 1); }
  :where(*:not(.terminal-host))[data-scrolling] { scrollbar-color: color-mix(in srgb, var(--fg-2) 45%, transparent) transparent; }
}
@supports not (-moz-appearance: none) {
  *:not(.terminal-host)::-webkit-scrollbar { width: 0.5rem; height: 0.5rem; background: transparent; }
  *:not(.terminal-host)::-webkit-scrollbar-button { display: none; }
  *:not(.terminal-host)::-webkit-scrollbar-track { background: transparent; }
  *:not(.terminal-host)::-webkit-scrollbar-thumb { background: transparent; border-radius: 0.25rem; transition: background-color 240ms cubic-bezier(0.23, 1, 0.32, 1); }
  *:not(.terminal-host)[data-scrolling]::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--fg-2) 45%, transparent); }
}
}
@media (prefers-reduced-motion: reduce) {
  :where(*:not(.terminal-host)), *:not(.terminal-host)::-webkit-scrollbar-thumb { transition: none; }
}

/* Sidebar card (sidebar-card.ts): the shell of every card that sits inside the sidebar above its footer, inside the
   sidebar's 8 px padding, as a quiet tinted card (no second glass layer inside the glass). The Inbox (inbox.ts) is the
   first, the version and licence panel the second. Only opacity and transform move, and the transform is cleared at rest. */
.ui-sidebar-card {
  display: grid; flex: 0 1 auto; gap: 0.25rem; min-height: 0; margin: auto 0 0.5rem; padding: 0.5rem;
  border-radius: calc(var(--r-lg, 1.125rem) - 0.5rem); background: color-mix(in oklab, var(--fg) 6%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fg) 12%, transparent);
  transition: opacity 160ms var(--ease-out, ease-out), transform 180ms var(--ease-out, ease-out);
}
/* The Inbox says "something waits for you": it takes the accent. */
.ui-inbox { flex: none; background: color-mix(in oklab, var(--accent) 9%, transparent); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--accent) 22%, transparent); }
/* A card of its own, like the toast: the glass halo would only smear its glyphs (and the solid action takes none). */
.ui-sidebar-card, .ui-sidebar-card * { text-shadow: none; }
.ui-sidebar-card[hidden] { display: none; }
.ui-sidebar-card[data-open="false"] { opacity: 0; transform: translateY(0.375rem); pointer-events: none; }
/* The first card shown pushes itself and what follows to the bottom; a card after it, and the footer, follow it directly.
   Only a card that takes part in the layout counts: one that was shown and then hidden stays in the DOM as `display: none`,
   and matching it would leave nothing to push the footer down (#259). */
.ui-sidebar-card:not([hidden]) ~ .ui-sidebar-card, .ui-sidebar-card:not([hidden]) ~ .ui-sidebar-foot { margin-top: 0; }
.ui-sidebar-card-header { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; min-height: 1.375rem; padding: 2px 0.375rem 0; color: var(--ui-text-secondary); font-size: var(--text-caption); font-weight: var(--fw-label); line-height: 1.4; }
/* The card's close control: an icon button, 24 px on desktop; its hit area is 44 px on touch without making the header taller. */
.ui-sidebar-card-close { position: relative; display: grid; flex: none; place-items: center; width: 1.5rem; height: 1.5rem; margin: -2px -0.25rem -2px 0; padding: 0; border: 0; border-radius: 999px; background: transparent; color: var(--fg-3); cursor: pointer; transition: transform 140ms var(--ease-out, ease-out), background-color 140ms ease, color 140ms ease; }
.ui-sidebar-card-close svg { width: 0.875rem; height: 0.875rem; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
.ui-sidebar-card-close:active { transform: scale(var(--ui-press-scale)); }
@media (hover: hover) and (pointer: fine) { .ui-sidebar-card-close:hover { background: color-mix(in oklab, var(--fg) 8%, transparent); color: var(--fg); } }
@media (pointer: coarse) { .ui-sidebar-card-close::after { content: ""; position: absolute; inset: -0.625rem; } }
.ui-inbox-count { font-variant-numeric: tabular-nums; }
.ui-inbox-list { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2px; }
.ui-inbox-row {
  display: flex; align-items: center; gap: 0.5rem; width: 100%; min-height: var(--row); padding: 0 0.375rem; border: 0; border-radius: 0.5rem;
  background: none; color: var(--fg); font: inherit; font-size: var(--text-body); font-weight: var(--fw-label); text-align: left; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 120ms ease, transform 140ms var(--ease-out, ease-out);
}
@media (hover: hover) and (pointer: fine) { .ui-inbox-row:hover { background: color-mix(in oklab, var(--fg) 6%, transparent); } }
.ui-inbox-row:active { transform: scale(var(--ui-press-scale-soft)); }
.ui-inbox-glyph, .ui-inbox-chevron { flex: none; width: 1rem; height: 1rem; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.ui-inbox-glyph { color: var(--accent); }
.ui-inbox-chevron { margin-left: auto; width: 0.875rem; height: 0.875rem; color: var(--ui-text-secondary); transition: transform 180ms var(--ease-out, ease-out); }
.ui-inbox-item[data-expanded="true"] .ui-inbox-chevron { transform: rotate(90deg); }
.ui-inbox-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The panel's height is a grid track, so opening is a plain CSS transition: it can be reversed mid-way and costs no script. */
.ui-inbox-panel { display: grid; grid-template-rows: 0fr; visibility: hidden; transition: grid-template-rows 180ms var(--ease-out, ease-out), visibility 0s 180ms; }
.ui-inbox-item[data-expanded="true"] .ui-inbox-panel { grid-template-rows: 1fr; visibility: visible; transition-delay: 0s; }
.ui-inbox-panel-inner { display: grid; gap: 0.5rem; min-height: 0; overflow: hidden; padding: 0 0.375rem 0 1.875rem; opacity: 0; transition: opacity 150ms ease, padding-bottom 180ms var(--ease-out, ease-out); }
.ui-inbox-item[data-expanded="true"] .ui-inbox-panel-inner { opacity: 1; padding-bottom: 0.375rem; }
.ui-inbox-body { margin: 0; padding-top: 2px; color: var(--ui-text-secondary); font-size: var(--text-secondary); line-height: 1.4; }
.ui-inbox-actions { display: flex; justify-content: flex-end; gap: 0.375rem; }
.ui-inbox-action { --ui-button-height: 1.75rem; --ui-button-font: 500 var(--text-secondary)/1 var(--font-ui, "Geist Mono", ui-monospace, monospace); min-height: 1.75rem; padding: 0 0.75rem; border-radius: 0.5rem; }
@media (pointer: coarse) { .ui-sidebar-card { padding: 0.375rem; } .ui-inbox-action { --ui-button-height: 2.75rem; min-height: 2.75rem; } }
@media (prefers-reduced-motion: reduce) { .ui-sidebar-card, .ui-sidebar-card[data-open="false"] { transform: none; transition: opacity 150ms ease; } .ui-inbox-panel, .ui-inbox-panel-inner, .ui-inbox-chevron, .ui-inbox-row { transition-duration: 1ms; } .ui-inbox-panel-inner { transition: opacity 150ms ease; } }


/* A card that grows out of its pill (sidebar-card-morph.ts): the script sets the pill's box as an inline transform and
   the transition below takes it to rest, so nothing but transform and opacity moves. The card sits above the pill while it
   travels; its content fades in after the shape is mostly there and out before it shrinks. A keyboard open has no motion. */
.ui-sidebar-card[data-morph="from"], .ui-sidebar-card[data-morph="in"], .ui-sidebar-card[data-morph="out"] { position: relative; z-index: 1; transform-origin: 0 0; }
.ui-sidebar-card[data-morph="in"] { transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 90ms ease-out; }
.ui-sidebar-card[data-morph="out"] { transition: transform 180ms cubic-bezier(0.23, 1, 0.32, 1), opacity 110ms ease-out 50ms; }
.ui-sidebar-card[data-morph="from"] > *, .ui-sidebar-card[data-morph="out"] > * { opacity: 0; }
.ui-sidebar-card[data-morph="in"] > * { transition: opacity 130ms ease-out 70ms; }
.ui-sidebar-card[data-morph="out"] > * { transition: opacity 60ms ease-out; }
.ui-sidebar-card[data-morph="none"], .ui-sidebar-card[data-morph="none"] > * { transition: none; }

/* Scroll fade (scroll-fade.ts): a scrolling body fades the edge that has more behind it, so a row is never cut at a hard line. */
.ui-scroll-fade[data-fade~="bottom"] { -webkit-mask-image: linear-gradient(#000 calc(100% - 1.375rem), transparent); mask-image: linear-gradient(#000 calc(100% - 1.375rem), transparent); }
.ui-scroll-fade[data-fade~="top"] { -webkit-mask-image: linear-gradient(transparent, #000 1.375rem); mask-image: linear-gradient(transparent, #000 1.375rem); }
.ui-scroll-fade[data-fade="top bottom"] { -webkit-mask-image: linear-gradient(transparent, #000 1.375rem, #000 calc(100% - 1.375rem), transparent); mask-image: linear-gradient(transparent, #000 1.375rem, #000 calc(100% - 1.375rem), transparent); }

/* A field that takes the place of a name in its parent's flow (`inlineRename`): the width of the name, the text where the name's text was. */
.ui-inline-rename-flow { width: 100%; margin: 0 -0.375rem; }
.ui-inline-rename::placeholder { color: var(--fg-3); }

/* A value run (kv-decorate.ts `parts`): small items that wrap only between them; each is one unit, tinted, with its note one
   step quieter than the name but still readable (--fg-2 clears 4.5:1 on the tint). */
.ui-kv-part { display: inline-flex; max-width: 100%; margin: 1px 0; padding: 0 0.4375rem; border-radius: 999px; background: color-mix(in oklab, var(--fg) 9%, transparent); font-size: var(--text-secondary, 0.75rem); line-height: 1.25rem; vertical-align: top; }
.ui-kv-part-name { min-width: 4ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-kv-part-note { flex: none; margin-left: 0.4em; color: var(--fg-2); font-variant-numeric: tabular-nums; }
/* A pressable value with a chevron (kv-decorate.ts `chevron`): the room for the chevron is the value's own padding. */
.ui-kv-value.ui-kv-has-chevron { position: relative; padding-right: 1.25rem; }

/* ---- The slider (slider.ts, #283): a native range input, flat. The fill up to the thumb is --fill, written on every input. ---- */
.ui-slider { display: flex; align-items: center; gap: 0.75rem; }
.ui-slider-input { --fill: 50%; -webkit-appearance: none; appearance: none; flex: 1; min-width: 0; height: 1.75rem; margin: 0; padding: 0; background: none; cursor: pointer; touch-action: pan-y; }
.ui-slider-input:focus { outline: none; }
.ui-slider-input::-webkit-slider-runnable-track { height: 0.25rem; border-radius: 2px; background: linear-gradient(to right, var(--fg-2) var(--fill), var(--hover) var(--fill)); }
.ui-slider-input::-moz-range-track { height: 0.25rem; border-radius: 2px; background: var(--hover); }
.ui-slider-input::-moz-range-progress { height: 0.25rem; border-radius: 2px; background: var(--fg-2); }
.ui-slider-input::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 1rem; height: 1rem; margin-top: -0.375rem; border: 0; border-radius: 50%; background: var(--fg); transition: transform 120ms var(--ease-out, ease-out); }
.ui-slider-input::-moz-range-thumb { width: 1rem; height: 1rem; border: 0; border-radius: 50%; background: var(--fg); transition: transform 120ms var(--ease-out, ease-out); }
.ui-slider-input:active::-webkit-slider-thumb { transform: scale(1.12); }
.ui-slider-input:active::-moz-range-thumb { transform: scale(1.12); }
[data-focus-ring="on"] .ui-slider-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 0.375rem; }
.ui-slider-value { flex: none; min-width: 5ch; text-align: right; color: var(--fg-2); font: var(--ui-type-label); font-variant-numeric: tabular-nums; letter-spacing: var(--ui-tracking); }
@media (pointer: coarse) { .ui-slider-input { height: 2.75rem; } .ui-slider-input::-webkit-slider-thumb { width: 1.375rem; height: 1.375rem; margin-top: -0.5625rem; } .ui-slider-input::-moz-range-thumb { width: 1.375rem; height: 1.375rem; } }
@media (prefers-reduced-motion: reduce) { .ui-slider-input::-webkit-slider-thumb, .ui-slider-input::-moz-range-thumb { transition: none; } }
