/* ==========================================================================
   lightweightDBClient - web UI
   Design tokens from the DB Studio redesign mockup.
   Both old token names and new mockup names are defined; new names are the
   single source of truth, old names are aliases so existing rules keep working.
   Three themes: dark (default), light, matrix (unchanged from original).
   ========================================================================== */

:root {
  /* IBM Plex Sans + Mono are loaded via Google Fonts in index.html.
     System stacks are the fallback when the network is unavailable. */
  --font-sans: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI Variable Text',
               'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, 'SF Mono', 'Cascadia Mono',
               'Segoe UI Mono', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;

  --radius: 10px;
  --radius-sm: 7px;

  /* Width of the icon rail. The sidebar overlay and its backdrop are absolutely
     positioned against it, so the number has to be readable from both. */
  --rail-w: 52px;
  --gap: 0px;   /* layout gaps now come from padding/border, not grid-gap */
}

/* ---------- theme: dark ---------- */
body.theme-dark {
  color-scheme: dark;

  /* ── Mockup tokens ── */
  --bg:      #0E1116;
  --panel:   #141A23;
  --panel2:  #1A212B;
  --panel3:  #212934;
  --border:  #242C38;
  --bstrong: #333E4D;
  --text:    #E7ECF4;
  --t2:      #9BA6B8;
  --t3:      #6C7788;
  --accent:  #4CC2FF;
  --asoft:   rgba(76,194,255,.13);
  --aink:    #062434;
  --green:   #43BE72;
  --amber:   #D6A03B;
  --red:     #EF6A4C;
  --violet:  #A78BFA;
  --pink:    #F178B6;
  --row:     #121822;
  --shadow:  0 24px 70px rgba(0,0,0,.55);

  /* ── Aliases (keep old names working) ── */
  --bg-elev:          var(--panel2);
  --panel-head:       var(--panel2);
  --muted:            var(--t2);
  --muted-2:          var(--t3);
  --border-strong:    var(--bstrong);
  --input-bg:         #0B1018;
  --input-text:       var(--text);
  --accent-contrast:  var(--aink);
  --accent-soft:      var(--asoft);
  --ok:               var(--green);
  --warn:             var(--amber);
  --danger:           var(--red);
  --danger-text:      #F4836A;
  --danger-soft:      rgba(239,106,76,.12);
  --danger-border:    rgba(239,106,76,.4);
  --warn-soft:        rgba(214,160,59,.12);
  --warn-border:      rgba(214,160,59,.4);
  --grid-head:        var(--panel);
  --row-alt:          var(--row);
  --row-hover:        var(--asoft);
  --scroll-thumb:     var(--bstrong);
  --scrim:           rgba(4,7,12,.46);
}

/* ---------- theme: light ---------- */
body.theme-light {
  color-scheme: light;

  /* ── Mockup tokens ── */
  --bg:      #F4F6F9;
  --panel:   #FFFFFF;
  --panel2:  #F7F9FC;
  --panel3:  #EEF1F6;
  --border:  #E2E7EF;
  --bstrong: #CBD3DF;
  --text:    #0F1520;
  --t2:      #5A6577;
  --t3:      #8B95A6;
  --accent:  #0B72CC;
  --asoft:   rgba(11,114,204,.09);
  --aink:    #FFFFFF;
  --green:   #2E9E5B;
  --amber:   #B4791C;
  --red:     #D2452A;
  --violet:  #7C5CE0;
  --pink:    #C0468C;
  --row:     #FBFCFE;
  --shadow:  0 24px 60px rgba(15,22,36,.16);

  /* ── Aliases ── */
  --bg-elev:          var(--panel2);
  --panel-head:       var(--panel2);
  --muted:            var(--t2);
  --muted-2:          var(--t3);
  --border-strong:    var(--bstrong);
  --input-bg:         #FFFFFF;
  --input-text:       var(--text);
  --accent-contrast:  var(--aink);
  --accent-soft:      var(--asoft);
  --ok:               var(--green);
  --warn:             var(--amber);
  --danger:           var(--red);
  --danger-text:      #BB3C24;
  --danger-soft:      #FEF2F2;
  --danger-border:    #FECACA;
  --warn-soft:        #FFFBEB;
  --warn-border:      #FDE68A;
  --grid-head:        var(--panel3);
  --row-alt:          var(--row);
  --row-hover:        var(--asoft);
  --scroll-thumb:     var(--bstrong);
  --scrim:           rgba(15,23,42,.26);
}

/* ---------- theme: matrix (unchanged) ---------- */
body.theme-matrix {
  color-scheme: dark;
  --radius: 0px;
  --radius-sm: 0px;
  --font-sans: var(--font-mono);
  --bg: #000000;
  --bg-elev: #000000;
  --panel: #000000;
  --panel-head: #001400;
  --panel2: #001100;
  --panel3: #001100;
  --text: #00ff00;
  --muted: #00d000;
  --muted-2: #00a000;
  --t2: #00d000;
  --t3: #00a000;
  --border: #00ff00;
  --border-strong: #00ff00;
  --bstrong: #00ff00;
  --input-bg: #000000;
  --input-text: #00ff00;
  --accent: #00ff00;
  --accent-contrast: #000000;
  --aink: #000000;
  --accent-soft: rgba(0, 255, 0, 0.14);
  --asoft: rgba(0, 255, 0, 0.14);
  --danger: #ff5555;
  --red: #ff5555;
  --danger-text: #ff7777;
  --danger-soft: rgba(255, 0, 0, 0.12);
  --danger-border: #ff5555;
  --warn: #ccff00;
  --amber: #ccff00;
  --warn-soft: rgba(204, 255, 0, 0.1);
  --warn-border: #ccff00;
  --ok: #00ff00;
  --green: #00ff00;
  --violet: #66ff66;
  --pink: #9dff9d;
  --row: rgba(0, 255, 0, 0.045);
  --grid-head: #001400;
  --row-alt: rgba(0, 255, 0, 0.045);
  --row-hover: rgba(0, 255, 0, 0.13);
  --shadow: none;
  --scroll-thumb: #00a000;
  --scrim: rgba(0,0,0,.68);
}

/* ---------- theme: christmas ----------
   Palette shared with the control-panel app so the two look related. Every
   rule in this file reads tokens, so a theme is only a token block. */
body.theme-christmas {
  color-scheme: dark;

  --bg:      #0B1F13;
  --panel:   #123822;
  --panel2:  #16412A;
  --panel3:  #1E4D2B;
  --border:  #2F6B40;
  --bstrong: #3E8A54;
  --text:    #F5F5F0;
  --t2:      #B7CFBE;
  --t3:      #86A891;
  --accent:  #FFD700;
  --asoft:   rgba(255,215,0,.14);
  --aink:    #1B2A12;
  --green:   #2ECC71;
  --amber:   #F2994A;
  --red:     #E63946;
  --violet:  #B48CFF;
  --pink:    #FF8FA3;
  --row:     #0F2A19;
  --shadow:  0 24px 70px rgba(0,0,0,.6);

  --bg-elev:          var(--panel2);
  --panel-head:       var(--panel2);
  --muted:            var(--t2);
  --muted-2:          var(--t3);
  --border-strong:    var(--bstrong);
  --input-bg:         #0A1A10;
  --input-text:       var(--text);
  --accent-contrast:  var(--aink);
  --accent-soft:      var(--asoft);
  --ok:               var(--green);
  --warn:             var(--amber);
  --danger:           var(--red);
  --danger-text:      #FF6B75;
  --danger-soft:      rgba(230,57,70,.14);
  --danger-border:    rgba(230,57,70,.45);
  --warn-soft:        rgba(242,153,74,.14);
  --warn-border:      rgba(242,153,74,.45);
  --grid-head:        var(--panel);
  --row-alt:          var(--row);
  --row-hover:        var(--asoft);
  --scroll-thumb:     var(--bstrong);
  --scrim:           rgba(4,7,12,.46);
}

/* ---------- theme: dragon ---------- */
body.theme-dragon {
  color-scheme: dark;
  --radius: 4px;
  --radius-sm: 3px;
  /* Serif body text is this theme's signature; the editor keeps --font-mono. */
  --font-sans: Georgia, 'Iowan Old Style', 'Times New Roman', 'Liberation Serif', serif;

  --bg:      #1A0E0A;
  --panel:   #2B1410;
  --panel2:  #331A14;
  --panel3:  #3D1B16;
  --border:  #5C2020;
  --bstrong: #7A2E2A;
  --text:    #F5E6D3;
  --t2:      #C9AE95;
  --t3:      #9A8069;
  --accent:  #FF6B1A;
  --asoft:   rgba(255,107,26,.15);
  --aink:    #24100A;
  --green:   #4CAF50;
  --amber:   #E6A817;
  --red:     #FF3B3B;
  --violet:  #C08BFF;
  --pink:    #FF8FB1;
  --row:     #200F0B;
  --shadow:  0 24px 70px rgba(0,0,0,.6);

  --bg-elev:          var(--panel2);
  --panel-head:       var(--panel2);
  --muted:            var(--t2);
  --muted-2:          var(--t3);
  --border-strong:    var(--bstrong);
  --input-bg:         #150A07;
  --input-text:       var(--text);
  --accent-contrast:  var(--aink);
  --accent-soft:      var(--asoft);
  --ok:               var(--green);
  --warn:             var(--amber);
  --danger:           var(--red);
  --danger-text:      #FF7A7A;
  --danger-soft:      rgba(255,59,59,.14);
  --danger-border:    rgba(255,59,59,.45);
  --warn-soft:        rgba(230,168,23,.14);
  --warn-border:      rgba(230,168,23,.45);
  --grid-head:        var(--panel);
  --row-alt:          var(--row);
  --row-hover:        var(--asoft);
  --scroll-thumb:     var(--bstrong);
  --scrim:           rgba(4,7,12,.46);
}

/* ==========================================================================
   base
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -0.01em; }
p { margin: 0; }

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

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.clipboard-helper {
  position: fixed;
  top: -1000px;
  left: -1000px;
  opacity: 0;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
body.theme-matrix :focus-visible { outline-offset: 1px; }

* { scrollbar-width: thin; scrollbar-color: var(--scroll-thumb) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb);
  border: 2px solid transparent;
  background-clip: padding-box;
  border-radius: var(--radius-sm);
}
::-webkit-scrollbar-thumb:hover { background: var(--accent); background-clip: padding-box; }
::-webkit-scrollbar-corner { background: transparent; }

::selection { background: var(--asoft); color: var(--text); }

/* ==========================================================================
   app shell
   ========================================================================== */

.app {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
}

/* ── top bar ── */
.app-topbar {
  flex: 0 0 52px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 14px 0 16px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  min-width: 0;
}

.topbar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}

.brand-mark {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--aink);
}

.topbar-brand-name {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -.01em;
  white-space: nowrap;
}

/* breadcrumb: active connection context */
.topbar-breadcrumb {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-left: 14px;
  border-left: 1px solid var(--border);
  height: 26px;
  flex: 0 0 auto;
  min-width: 0;
}

.breadcrumb-dot {
  flex: 0 0 auto;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--db-color, var(--green));
}

.breadcrumb-db {
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
}

.breadcrumb-info {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--t3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 260px;
}

/* ⌘K palette trigger (stretched center) */
.topbar-search {
  flex: 1;
  display: flex;
  justify-content: center;
  min-width: 0;
}

.palette-trigger {
  display: flex;
  align-items: center;
  gap: 10px;
  width: min(460px, 42vw);
  height: 32px;
  padding: 0 10px;
  border: 1px solid var(--border);
  background: var(--panel2);
  border-radius: 9px;
  cursor: pointer;
  color: var(--t3);
  font: inherit;
  font-size: 12.5px;
  text-align: left;
  transition: border-color .14s, color .14s;
}
.palette-trigger:hover { border-color: var(--bstrong); color: var(--t2); }

.palette-placeholder { flex: 1; }

.palette-kbd {
  font-family: var(--font-mono);
  font-size: 10.5px;
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 2px 5px;
  flex: 0 0 auto;
  white-space: nowrap;
}

/* right-side topbar cluster */
.topbar-actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
}

/* server health pill */
.health-pill {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 10px;
  border-radius: 8px;
  background: var(--panel2);
  border: 1px solid var(--border);
  white-space: nowrap;
}
.health-pill-bad { border-color: var(--danger-border); }

.health-dot {
  flex: 0 0 auto;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--green);
  animation: pulse-dot 2.4s ease-in-out infinite;
}
.health-pill-bad .health-dot { background: var(--red); animation: none; }

@keyframes pulse-dot { 0%,100%{opacity:1} 50%{opacity:.3} }

.health-label {
  font-size: 11.5px;
  color: var(--t2);
}

.health-latency {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--t3);
}

/* dark / light toggle */
.theme-select {
  height: 28px;
  padding: 0 8px;
  border-radius: 8px;
  font: inherit;
  font-size: 11.5px;
  cursor: pointer;
  color: var(--text);
  background: var(--panel2);
  border: 1px solid var(--border);
}
.theme-select:hover { border-color: var(--bstrong); }
.theme-select option { background: var(--panel); color: var(--text); }

.topbar-btn {
  height: 28px;
  padding: 0 12px;
  border-radius: 8px;
  border-color: var(--border);
  background: var(--panel2);
}

/* keep legacy .app-header selector pointing at the same element via alias */
.app-header { display: none; }

/* ── error banner ── */
.banner {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 10px 12px 0;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  font-size: 13px;
}
.banner-error {
  color: var(--danger-text);
  background: var(--danger-soft);
  border: 1px solid var(--danger-border);
}
.banner-icon { display: flex; padding-top: 1px; color: var(--danger); flex: 0 0 auto; }
.banner-text { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; white-space: pre-wrap; }
.banner-close { color: var(--danger); margin: -4px -4px 0 0; }
.banner-close:hover:not(:disabled) { color: var(--danger-text); border-color: var(--danger-border); }

/* ── app body ── */
.app-body {
  flex: 1 1 auto;
  display: flex;
  min-height: 0;
  /* Containing block for the sidebar overlay and its backdrop, both of which
     are absolute and must stop at the top bar rather than at the viewport. */
  position: relative;
}

/* ==========================================================================
   icon rail (52px)
   ========================================================================== */

.app-rail {
  flex: 0 0 var(--rail-w);
  /* Above the backdrop: dismissing the flyout and switching to another view are
     the same click, so the rail must stay reachable while it is open. */
  position: relative;
  z-index: 31;
  background: var(--panel);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 10px 0;
  gap: 4px;
}

.rail-item {
  width: 34px; height: 34px;
  border-radius: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--t3);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: background .12s, color .12s;
}
.rail-item:hover:not(:disabled) { background: var(--panel2); color: var(--text); }
.rail-item.rail-active { background: var(--asoft); color: var(--accent); }
.rail-item:disabled { opacity: .4; cursor: not-allowed; }

.rail-spacer { flex: 1; }

/* ==========================================================================
   sidebar (290px)
   ========================================================================== */

/* An overlay, not a column. Docked, the rail and the sidebar took ~342px off
   the grid on every screen, whether or not anything in them was being read; as
   a flyout the grid keeps that width and the panel is one rail click away.
   .app-body owns the open state so the backdrop and the resizer can key off the
   same class. */
.sidebar {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--rail-w);
  z-index: 30;
  width: var(--sidebar-w, 290px);
  background: var(--panel);
  border-right: 1px solid var(--border);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  transform: translateX(-100%);
  visibility: hidden;
  transition: transform .16s ease, visibility 0s linear .16s;
}

.app-body.sidebar-open .sidebar {
  transform: none;
  visibility: visible;
  transition: transform .16s ease, visibility 0s;
}

/* Dims the content the flyout covers. Inset from the rail, so the rail reads as
   part of the chrome rather than as something behind a scrim. */
.sidebar-backdrop {
  position: absolute;
  inset: 0 0 0 var(--rail-w);
  z-index: 29;
  background: var(--scrim);
  opacity: 0;
  transition: opacity .16s ease;
}
.app-body.sidebar-open .sidebar-backdrop { opacity: 1; }

/* Someone who has asked for less motion gets the panel, not the slide. */
@media (prefers-reduced-motion: reduce) {
  .sidebar,
  .app-body.sidebar-open .sidebar,
  .sidebar-backdrop { transition: none; }
}

/* Drag handle on the flyout's right edge. A hair wider than its visible line so
   it's easy to grab without being a fat, always-there border. It rides the
   overlay rather than sitting between two columns, so it is only there — and
   only hit-testable — while the overlay is open. */
.sidebar-resizer {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--rail-w) + var(--sidebar-w, 290px) - 2px);
  width: 5px;
  z-index: 32;
  cursor: col-resize;
  background: transparent;
  visibility: hidden;
}
.app-body.sidebar-open .sidebar-resizer { visibility: visible; }

.sidebar-resizer::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0; left: 2px;
  width: 1px;
  background: transparent;
}
.sidebar-resizer:hover::after,
.sidebar-resizer.is-dragging::after {
  background: var(--accent);
}

/* Sidebar sections. The icon rail shows exactly one at a time, so the visible
   section owns the full column height and scrolls its own body. */
.sidebar-section {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.sidebar-section-flex {
  flex: 1 1 auto;
  overflow: hidden;
}

.sidebar-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 12px 14px 10px;
  flex: 0 0 auto;
}

.sidebar-section-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .09em;
  color: var(--t3);
}

.sidebar-section-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}

.sidebar-section-body {
  padding: 0 14px 12px;
  overflow-y: auto;
}

.sidebar-section-body-flex {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 0;
  padding-bottom: 12px;
}

.sidebar-section-body-flex .schema-content {
  max-height: none;
}

/* ==========================================================================
   connection cards (new design)
   ========================================================================== */

.conn-list { display: flex; flex-direction: column; gap: 6px; }

.conn-card {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 10px 11px;
  border-radius: 10px;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--panel2);
  transition: border-color .14s;
}
.conn-card:hover { border-color: var(--bstrong); }
.conn-card.conn-card-active { border-color: var(--accent); background: var(--asoft); }

.conn-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
}

.conn-card-dot {
  flex: 0 0 auto;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--t3);
}
.conn-card-dot-online  { background: var(--green); }
.conn-card-dot-idle    { background: var(--amber); }
.conn-card-dot-offline { background: var(--t3); }

.conn-card-name {
  flex: 1;
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.conn-card-ping {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--t3);
  flex: 0 0 auto;
}

.conn-card-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.conn-driver-tag {
  flex: 0 0 auto;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .07em;
  padding: 2px 6px;
  border-radius: 5px;
  white-space: nowrap;
  text-transform: uppercase;
}

.conn-dsn-host {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--t3);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Selecting connections, and acting on the selection.

   One checkbox per row and one bar of actions above the list, instead of the
   four icon buttons that used to sit on every row. The bar appears only while
   something is checked: an untouched list shows names, not controls.
   -------------------------------------------------------------------------- */

/* Two rows by design - what is selected, then what can be done to it - rather
   than one row left to wrap wherever the sidebar happens to end. */
.conn-bulk {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 6px;
  padding: 7px 8px;
  border: 1px solid var(--bstrong);
  border-radius: var(--radius-sm);
  background: var(--panel2);
}

.conn-bulk-pick {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  user-select: none;
}

.conn-bulk-count {
  font-size: 11px;
  font-weight: 600;
  color: var(--t2);
  white-space: nowrap;
}

.conn-bulk-actions {
  display: flex;
  align-items: stretch;
  gap: 4px;
}

/* The four labelled actions share the row equally, so the bar reads as one
   control rather than four buttons of accidental widths. */
.conn-bulk-actions .btn {
  flex: 1 1 0;
  min-width: 0;
  padding: 4px 2px;
  font-size: 11px;
  white-space: nowrap;
  /* A label that cannot fit is cut with an ellipsis rather than spilling over
     the button beside it. */
  overflow: hidden;
  text-overflow: ellipsis;
}
.conn-bulk-actions .conn-bulk-edit { flex: 0 0 auto; padding: 4px 6px; }
.conn-bulk-actions .btn:disabled { opacity: .45; }

/* An armed delete takes the whole row: it says how many it is about to delete,
   which does not fit a fifth of a 250px sidebar - and while a destructive action
   is waiting to be confirmed, the other actions are not what the click is for.
   Browsers without :has() keep five buttons, ellipsised; nothing breaks. */
.conn-bulk-actions .btn.armed { flex: 1 1 auto; }
.conn-bulk-actions:has(.btn.armed) .btn:not(.armed) { display: none; }

/* Destructive, but not shouting from the resting state: the colour arrives on
   hover and on the armed confirmation. */
.btn-danger-ghost:hover:not(:disabled) {
  color: var(--danger);
  border-color: var(--danger-border);
  background: var(--danger-soft);
}
.btn.armed {
  color: var(--danger);
  border-color: var(--danger);
  background: var(--danger-soft);
}

/* The checkbox cell on a row. A label so the whole cell is a hit target; it is
   only on screen while the pointer is on the row or the row is checked, so the
   resting list is names and nothing else. */
.conn-pick {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  padding: 0 1px;
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s;
}
.conn-pick input { cursor: pointer; margin: 0; }
.conn-row:hover .conn-pick,
.conn-row:focus-within .conn-pick,
.conn-row.is-selected .conn-pick { opacity: 1; }

/* ---------- the filter ----------
   Narrows by name or engine as you type; Escape clears it. Hidden below five
   saved connections, where the list can be read whole. */
.conn-filter-wrap {
  position: relative;
  display: flex;
  align-items: center;
  margin-bottom: 8px;
}
.conn-filter-wrap[hidden] { display: none; }

.conn-filter-icon {
  position: absolute;
  left: 9px;
  color: var(--t3);
  pointer-events: none;
}

.conn-filter {
  width: 100%;
  height: 30px;
  padding: 0 26px 0 28px;
  font-family: var(--font-sans);
  font-size: 12px;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.conn-filter::placeholder { color: var(--t3); }
.conn-filter:focus { outline: none; border-color: var(--accent); }
/* Only worth its place once there is something to clear. */
.conn-filter-clear {
  position: absolute;
  right: 5px;
  display: none;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  color: var(--t3);
  background: transparent;
  border: 0;
  border-radius: 5px;
  cursor: pointer;
}
.conn-filter:not(:placeholder-shown) ~ .conn-filter-clear { display: flex; }
.conn-filter-clear:hover { color: var(--text); background: var(--panel3); }

/* ---------- section label (OPEN) ---------- */
.conn-section {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 4px 4px;
}
.conn-section-name {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .08em;
  color: var(--t3);
}
.conn-section-rule { flex: 1 1 auto; height: 1px; background: var(--border); }

/* ---------- engine group header ----------
   The group carries the engine, which is what lets a row be nothing but a name.
   Folded state is remembered per engine (localStorage). */
.conn-group {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  padding: 7px 4px 5px;
  font-family: var(--font-sans);
  color: var(--t3);
  background: transparent;
  border: 0;
  cursor: pointer;
  text-align: left;
}
.conn-group:hover { color: var(--t2); }
.conn-group-caret { flex: 0 0 auto; transform: rotate(90deg); transition: transform .12s; }
.conn-group.is-folded .conn-group-caret { transform: rotate(0deg); }
.conn-group-dot {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--db-color, var(--t3));
}
.conn-group-name {
  flex: 1 1 auto;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .08em;
}
.conn-group-count { font-family: var(--font-mono); font-size: 10px; }

/* ---------- the row ----------
   A name, and the engine as a short coloured edge. The DSN it used to carry on
   a second line is the row's tooltip now. */
.conn-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding-right: 6px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.conn-row:hover { background: var(--panel3); }
.conn-row.is-selected { background: var(--asoft); }

.conn-edge {
  flex: 0 0 auto;
  width: 3px;
  height: 20px;
  margin-left: 4px;
  border-radius: 2px;
  background: var(--db-color, var(--bstrong));
}

.conn-name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.conn-row:hover .conn-name { color: var(--accent); }

/* Which environment dials it, for a host this machine cannot reach itself. */
.conn-via {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--t3);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 1px 5px;
  white-space: nowrap;
}
.conn-open {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--green);
}

/* Connect and the overflow menu, on hover. Keyboard users get them from
   :focus-within, which a tab into the row satisfies. */
.conn-acts {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 2px;
  opacity: 0;
  transition: opacity .12s;
}
.conn-row:hover .conn-acts,
.conn-row:focus-within .conn-acts { opacity: 1; }
/* The actions replace the "via" chip rather than pushing the name: a row is
   34px of width nobody has to spare. */
.conn-row:hover .conn-via,
.conn-row:focus-within .conn-via { display: none; }

.conn-act {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  color: var(--t2);
  background: transparent;
  border: 0;
  border-radius: 5px;
  cursor: pointer;
}
.conn-act:hover { color: var(--text); background: var(--bstrong); }

/* ---------- the row's overflow menu ---------- */
.conn-menu {
  position: absolute;
  top: 30px;
  right: 4px;
  z-index: 5;
  min-width: 132px;
  padding: 4px;
  display: flex;
  flex-direction: column;
  background: var(--panel2);
  border: 1px solid var(--bstrong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  cursor: default;
}
.conn-menu-item {
  font-family: var(--font-sans);
  font-size: 12px;
  text-align: left;
  padding: 6px 8px;
  color: var(--t2);
  background: transparent;
  border: 0;
  border-radius: 5px;
  cursor: pointer;
}
.conn-menu-item:hover { color: var(--text); background: var(--panel3); }
.conn-menu-item.is-danger { color: var(--danger-text); }
.conn-menu-item.is-danger:hover { background: var(--danger-soft); }
.conn-menu-item.armed { color: var(--danger); background: var(--danger-soft); }
.conn-menu-sep { height: 1px; margin: 3px 4px; background: var(--border); }

.tag {
  flex: 0 0 auto;
  padding: 1px 6px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--accent);
  background: var(--asoft);
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

/* ---------- statement strip ----------
   One tab per statement of a script that ran as several (runBatch in
   app-query.js). Sits between the column chips and the grid, so what it points
   at is directly below it, and it is hidden entirely for a single statement. */
.batch-tabs {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
  padding: 6px 10px;
  overflow-x: auto;
  border-bottom: 1px solid var(--border);
  background: var(--panel);
  scrollbar-width: thin;
  scrollbar-color: var(--bstrong) transparent;
}
.batch-tabs[hidden] { display: none; }
.batch-tabs::-webkit-scrollbar { height: 6px; }
.batch-tabs::-webkit-scrollbar-thumb { background: var(--bstrong); border-radius: 3px; }

.batch-tab {
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 4px 9px;
  font-family: var(--font-sans);
  font-size: 11px;
  color: var(--t2);
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
}
.batch-tab:hover { border-color: var(--bstrong); color: var(--text); }
.batch-tab.is-active {
  color: var(--text);
  border-color: var(--accent);
  background: var(--asoft);
}
/* The statement that stopped the script. Red at rest, not only when active:
   finding it is why the strip is worth looking at. */
.batch-tab.is-failed {
  color: var(--danger-text);
  border-color: var(--danger-border);
  background: var(--danger-soft);
}
.batch-tab-n {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--t3);
}
.batch-tab.is-active .batch-tab-n { color: var(--accent); }
.batch-tab-verb { font-weight: 600; letter-spacing: .02em; }
.batch-tab-meta { font-family: var(--font-mono); font-size: 10px; color: var(--t3); }
.batch-tabs-note {
  flex: 0 0 auto;
  margin-left: 4px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--t3);
  white-space: nowrap;
}

.form { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.conn-list:empty + .form { margin-top: 0; padding-top: 0; border-top: 0; }

/* ==========================================================================
   buttons, inputs
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  padding: 7px 12px;
  color: var(--text);
  background: var(--panel2);
  border: 1px solid var(--bstrong);
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  transition: color .14s, background-color .14s, border-color .14s;
}
.btn:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); }
.btn:disabled { opacity: .45; cursor: not-allowed; }

.btn-primary {
  color: var(--aink);
  background: var(--accent);
  border-color: var(--accent);
  font-weight: 600;
}
.btn-primary:hover:not(:disabled) {
  color: var(--aink);
  border-color: var(--accent);
  filter: brightness(1.1);
}

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

.btn-icon {
  padding: 6px;
  color: var(--t3);
  background: transparent;
  border-color: transparent;
}
.btn-icon:hover:not(:disabled) { color: var(--accent); border-color: var(--border); }
/* Armed, the trash grows a "Delete?" label: a colour change alone on a 29px
   icon is too quiet to be the only warning before a destructive click. */
.btn-icon.armed {
  gap: 4px;
  padding: 6px 8px;
  color: var(--danger);
  border-color: var(--danger-border);
  background: var(--danger-soft);
}
.btn-icon.armed:hover:not(:disabled) { color: var(--danger); border-color: var(--danger); }
.armed-label { font-size: 11px; font-weight: 600; line-height: 1; }

.btn-sm { padding: 5px 10px; font-size: 12px; }
.btn-xs { padding: 3px 8px; font-size: 11px; }
.btn-block { width: 100%; }

.icon { flex: 0 0 auto; display: block; }

kbd {
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1;
  padding: 3px 4px;
  color: inherit;
  border: 1px solid currentColor;
  border-radius: 3px;
  opacity: .7;
}
.kbd-group { display: inline-flex; gap: 2px; padding: 0; border: 0; opacity: 1; }
.btn-sm kbd { margin-left: 1px; }

.input {
  width: 100%;
  font: inherit;
  font-size: 13px;
  color: var(--input-text);
  background: var(--input-bg);
  border: 1px solid var(--bstrong);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
}
.input:hover:not(:focus):not(:disabled) { border-color: var(--t3); }
.input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--asoft);
}
.input:disabled { opacity: .55; cursor: not-allowed; }
.input::placeholder { color: var(--t3); opacity: .8; }

select.input { padding-right: 6px; cursor: pointer; }
.input-sm { padding: 5px 8px; font-size: 12.5px; width: auto; }
option { background: var(--bg); color: var(--text); }

textarea.input { resize: vertical; }
.dsn-input { font-size: 12px; line-height: 1.5; min-height: 52px; }

.field { display: block; margin-bottom: 10px; }
.field .label {
  display: block;
  margin-bottom: 4px;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--t2);
}
.field .opt {
  font-weight: 400;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--t3);
}

.field-search { position: relative; margin-bottom: 4px; }
.field-search-icon {
  position: absolute;
  left: 9px; top: 50%;
  transform: translateY(-50%);
  display: flex;
  color: var(--t3);
  pointer-events: none;
}
.input-search { padding-left: 30px; }

.hint {
  margin: -4px 0 10px;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--t3);
}

.form-title {
  margin: 4px 0 10px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--t3);
}
.form-actions { margin-top: 12px; }
/* Connect stays the wide primary action; Test sits beside it without competing
   for the eye. */
.form-actions-split {
  display: flex;
  gap: 6px;
  align-items: stretch;
}
.form-actions-split #connect-btn { flex: 1 1 auto; justify-content: center; }
.form-actions-split #test-btn { flex: 0 0 auto; }

/* URL / Fields switch above the connection inputs. */
.mode-tabs {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 2px;
  gap: 2px;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.mode-tab {
  appearance: none;
  padding: 4px 12px;
  font: inherit;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--t2);
  background: transparent;
  border: 0;
  border-radius: calc(var(--radius-sm) - 2px);
  cursor: pointer;
}
.mode-tab:hover:not(:disabled):not(.mode-tab-active) { color: var(--text); }
.mode-tab-active {
  color: var(--text);
  background: var(--bg);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .18);
}
.mode-tab:disabled { opacity: .45; cursor: not-allowed; }

/* The connection string the field values add up to, credentials masked. */
.param-preview {
  margin: 2px 0 10px;
  padding: 6px 8px;
  font-size: 11px;
  color: var(--t2);
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow-wrap: anywhere;
}

/* Test result: same slot for both answers, so it is always in one place. */
.form-note {
  margin-top: 10px;
  padding: 7px 10px;
  font-size: 12px;
  line-height: 1.5;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  overflow-wrap: anywhere;
}
.form-note-ok {
  color: var(--ok);
  background: var(--bg2);
  border-color: var(--ok);
}
.form-note-bad {
  color: var(--danger-text);
  background: var(--danger-soft);
  border-color: var(--danger-border);
}
.form-error {
  margin-top: 10px;
  padding: 8px 10px;
  font-size: 12px;
  color: var(--danger-text);
  background: var(--danger-soft);
  border: 1px solid var(--danger-border);
  border-radius: var(--radius-sm);
  overflow-wrap: anywhere;
  white-space: pre-wrap;
  line-height: 1.5;
  max-height: 40vh;
  overflow-y: auto;
}

.spinner {
  display: inline-block;
  flex: 0 0 auto;
  width: 13px; height: 13px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ==========================================================================
   main column
   ========================================================================== */

.main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  background: var(--bg);
}

/* ==========================================================================
   database type colours

   Each engine's own brand colour, so a connection is recognisable before its
   name is read. Deliberately outside the theme blocks: MongoDB is green in
   every theme, and a token that changed with the theme would defeat the point.

   A .db-<driver> class sets --db-color on a container; descendants read
   var(--db-color, <theme fallback>) so an unknown driver still renders.
   ========================================================================== */

.db-postgres      { --db-color: #4A90C4; }
.db-mysql         { --db-color: #E48E00; }
.db-mariadb       { --db-color: #C0765A; }
.db-sqlite        { --db-color: #3BA3E0; }
.db-sqlserver     { --db-color: #CC2927; }
.db-oracle        { --db-color: #F80000; }
.db-mongo         { --db-color: #47A248; }
.db-redis         { --db-color: #FF4438; }
.db-cassandra     { --db-color: #1287B1; }
.db-clickhouse    { --db-color: #FFCC01; }
.db-elasticsearch { --db-color: #00BFB3; }
.db-influxdb      { --db-color: #22ADF6; }
.db-dynamodb      { --db-color: #6B7FE8; }
.db-firestore     { --db-color: #FFA000; }
.db-couchbase     { --db-color: #EA2328; }
.db-surrealdb     { --db-color: #FF00A0; }

/* ==========================================================================
   session tabs (40px strip)
   ========================================================================== */

.tabs {
  flex: 0 0 40px;
  display: flex;
  align-items: stretch;
  gap: 2px;
  padding: 0 8px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
}

.tab {
  display: flex;
  align-items: center;
  gap: 0;
  padding-right: 4px;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  flex: 0 0 auto;
}
.tab:hover { border-bottom-color: var(--bstrong); }
/* The active tab is underlined in its engine's colour, so which database the
   editor is pointed at is legible from the strip alone. */
.tab-active { border-bottom-color: var(--db-color, var(--accent)); }

.tab-main {
  display: flex;
  align-items: center;
  gap: 8px;
  max-width: 220px;
  padding: 0 10px;
  font: inherit;
  font-size: 12.5px;
  color: var(--t3);
  text-align: left;
  background: transparent;
  border: 0;
  cursor: pointer;
}
.tab-active .tab-main { color: var(--text); }
.tab-main:hover { color: var(--text); }

.tab-dot {
  flex: 0 0 auto;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--db-color, var(--t3));
  opacity: .55;
}
.tab-active .tab-dot { opacity: 1; }

.tab-text { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
.tab-label {
  max-width: 160px;
  font-size: 12.5px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tab-sub {
  max-width: 160px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--t3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tab-close { padding: 5px; color: var(--t3); background: transparent; border: 0; cursor: pointer; }
.tab-close:hover { color: var(--text); }

.tabs-empty {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  font-size: 12px;
  color: var(--t3);
}

/* ==========================================================================
   panels
   ========================================================================== */

.panel {
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  min-width: 0;
}

.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 7px 12px;
  border-bottom: 1px solid var(--border);
  min-height: 44px;
}
.panel-header h2 {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--t3);
}
.panel-header-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }

.count { font-size: 11.5px; color: var(--t3); font-variant-numeric: tabular-nums; }

.panel-body { padding: 10px 12px 12px; }

.panel-editor { flex: 0 0 auto; }
.panel-editor .panel-body { display: flex; flex-direction: column; gap: 8px; }

/* ── editor toolbar (is the panel-header for #panel-query) ── */

/* One row: the twisty wirePanels() prepends, the buffer-tab strip, then the
   secondary actions, Limit, and Run. The tab strip is the flexible item, so it
   both fills the gap and pushes the rest to the right edge. */
.editor-toolbar {
  justify-content: flex-start !important;
  flex-wrap: wrap;
  gap: 6px !important;
  padding: 7px 12px !important;
}

.toolbar-limit {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: var(--t2);
  padding: 0 8px;
  height: 29px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

/* An input styled as text: the box has the toolbar chip's border already, and a
   second one around the field would read as two controls.

   Sized to what is in it, not to a number picked in advance. A plain number
   input defaults to about 150px of empty box, and the 5ch that replaced it was
   only right until someone typed a six-digit limit. field-sizing does it
   properly; the bounds stop it collapsing on an empty field or running away on
   a pasted one, and the width is the fallback where field-sizing is not
   supported. */
.toolbar-limit-val {
  /* auto, not a number: field-sizing only sizes to content when the width is
     auto - an explicit one still wins, which is what made 5ch stick. */
  width: auto;
  field-sizing: content;
  min-width: 3ch;
  max-width: 9ch;
  padding: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text);
  background: none;
  border: 0;
  text-align: right;
}

/* Without field-sizing, `width: auto` on a number input is ~150px of empty
   box. Pin it back to the old fixed width there. */
@supports not (field-sizing: content) {
  .toolbar-limit-val { width: 5ch; }
}

.toolbar-limit:focus-within { border-color: var(--accent); }
.toolbar-limit-val:focus { outline: none; }

/* The spinners cost more width than they earn at this size. */
.toolbar-limit-val::-webkit-inner-spin-button,
.toolbar-limit-val::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.toolbar-limit-val { -moz-appearance: textfield; appearance: textfield; }

/* The tab strip takes the slack.

   flex-basis 0, not auto: .editor-toolbar wraps, and flex wraps BEFORE it
   shrinks - so with an auto basis nine open tabs made the strip's content the
   line's hypothetical size, and Run went to a second row rather than the tabs
   scrolling. At basis 0 the strip contributes nothing to that decision, then
   grows into whatever the row has left. The toolbar keeps its wrap for the
   case it is actually for: a window too narrow for Limit, ... and Run
   together.

   min-width:0 is what lets it then be squeezed below its content, which is
   what hands the overflow to the strip's own scroller. */
.editor-toolbar > .buffer-tabs {
  flex: 1 1 0;
  min-width: 0;
}

/* In a toolbar the strip scrolls; it does not wrap. Wrapping would let a
   handful of open tabs grow the header a row at a time, which is the thing
   this move was undoing. */
.editor-toolbar .buftabs {
  flex-wrap: nowrap;
  margin-bottom: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}
.editor-toolbar .buftabs::-webkit-scrollbar { height: 0; }

/* Nothing in the row shrinks: the tabs keep their shape and scroll out of
   sight, and Limit, ... and Run keep theirs and stay put.

   Matched on the strip's direct children rather than on .buftabs-add and
   friends. Those classes never reach the DOM: app-saved.js declares a second
   global iconButton(name, title) that ignores the third argument, and it loads
   after app-core.js's three-argument one, so the class app-edittools.js asks
   for is dropped. Structure is what is actually there. */
.editor-toolbar .buftabs > * { flex: 0 0 auto; }

.editor-toolbar .toolbar-limit,
.editor-toolbar > .btn,
.editor-toolbar #run-btn { flex: 0 0 auto; }

/* ==========================================================================
   results area
   ========================================================================== */

.results-area {
  flex: 1 1 auto;
  display: flex;
  min-height: 0;
}

.panel-results {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-bottom: 0;
  overflow: hidden;
}

/* thin indeterminate progress line while a query is in flight */
.panel-results .panel-header { position: relative; overflow: hidden; }
.panel-results.loading .panel-header::after {
  content: '';
  position: absolute;
  left: 0; bottom: -1px;
  width: 45%; height: 2px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  animation: slide 1.1s linear infinite;
}
@keyframes slide {
  0% { transform: translateX(-110%); }
  100% { transform: translateX(330%); }
}

/* ==========================================================================
   row filter

   Lives in the results header where the disabled "coming soon" button used to.
   Sized to a chip rather than to a search field: it sits in a row of buttons,
   and a full-width input there would read as the panel's main control.
   ========================================================================== */

.row-filter {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 29px;
  padding: 0 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--input-bg);
  flex: 0 1 auto;
  min-width: 0;
}
.row-filter:focus-within { border-color: var(--accent); }
.row-filter:has(.row-filter-input:disabled) { opacity: .5; }

.row-filter-icon { color: var(--t3); flex: 0 0 auto; }

.row-filter-input {
  width: 13ch;
  min-width: 0;
  padding: 0;
  border: 0;
  outline: 0;
  background: none;
  color: var(--input-text);
  font-family: inherit;
  font-size: 12px;
}
.row-filter-input::placeholder { color: var(--t3); }
.row-filter-input:disabled { cursor: not-allowed; }

/* Chrome's own clear affordance, coloured to the theme instead of to its
   default grey-on-anything. */
.row-filter-input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
  width: 11px;
  height: 11px;
  cursor: pointer;
  background: currentColor;
  color: var(--t3);
  mask: 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='M18 6 6 18M6 6l12 12'/%3E%3C/svg%3E") center / contain no-repeat;
}
.row-filter-input::-webkit-search-cancel-button:hover { color: var(--text); }

/* The needle, echoed back inside the empty-state line. */
.grid-zero-needle { color: var(--text); font-family: var(--font-mono); }

/* ==========================================================================
   column chips

   One row between the results header and the grid, one chip per column. A
   single line that scrolls sideways rather than wrapping: the row is a control
   strip, and a nine-column table must not be able to push the grid down by
   three rows just by existing.
   ========================================================================== */

.col-chips {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 7px 12px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}
.col-chips::-webkit-scrollbar { height: 0; }

.col-chips-label {
  flex: 0 0 auto;
  padding-right: 3px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .1em;
  color: var(--t3);
}

.col-chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 22px;
  padding: 0 8px;
  border: 1px solid var(--bstrong);
  border-radius: var(--radius-sm);
  background: var(--panel2);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 10.5px;
  white-space: nowrap;
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}
.col-chip:hover { border-color: var(--accent); }

/* A hidden column reads as switched off, not as absent: it keeps its place in
   the row so bringing it back is where dropping it was. */
.col-chip.is-off {
  background: transparent;
  border-color: var(--border);
  color: var(--t3);
}

.col-chip-dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--accent);
  flex: 0 0 4px;
}
.col-chip.is-off .col-chip-dot { background: var(--t3); }

.col-chip-all {
  font-family: var(--font-sans);
  font-weight: 600;
  color: var(--accent);
  background: var(--asoft);
  border-color: transparent;
}
.col-chip-all:hover { border-color: var(--accent); }

/* ==========================================================================
   row detail panel (320px scaffold)
   ========================================================================== */

.row-detail {
  flex: 0 0 320px;
  display: flex;
  flex-direction: column;
  border-left: 1px solid var(--border);
  background: var(--panel);
  min-height: 0;
}

.row-detail-head {
  flex: 0 0 40px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 14px;
  border-bottom: 1px solid var(--border);
}

.row-detail-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .09em;
  color: var(--t3);
  flex: 0 0 auto;
}

.row-detail-id {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.row-detail-nav {
  display: flex;
  align-items: center;
  gap: 2px;
  flex: 0 0 auto;
}

.row-detail-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 6px 0;
}

/* One field per line. Built by rowDetailField() in app-rowdetail.js. */
.row-detail-field {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 7px 14px;
  border-bottom: 1px solid var(--border);
}

/* The name in full, wrapping rather than ellipsizing. Truncating it here would
   give up the one thing the panel exists to provide. */
.row-detail-key {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--t3);
  overflow-wrap: anywhere;
}

.row-detail-val {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text);
  word-break: break-all;
  white-space: pre-wrap;
}

/* A field whose column the chip row is hiding: still listed - the panel shows
   the whole record - but marked, so its absence from the grid is not a
   mystery. */
.row-detail-field.is-hidden-col .row-detail-key { opacity: .6; }
.row-detail-field.is-hidden-col .row-detail-key::after {
  content: ' · hidden';
  font-weight: 400;
  letter-spacing: 0;
}

/* An unsaved edit carries the plain .cell-pending class and nothing else, so
   the shared rule below - and matrix's override of it - paints the panel and
   the grid identically. A copy of the rule here would have been a second
   opinion about what "unsaved" looks like. */

/* The cell-* classes come along with cellDisplay()'s kind, which is the point -
   NULL is italic and dimmed here exactly as it is in the grid. Only the grid's
   numeric alignment has to go: right-aligned against a 320px panel edge, a
   number would be nowhere near the name it belongs to. */
.row-detail-val.cell-number { text-align: left; }

/* Editable in place, with the affordance .cell-editable gives a grid cell but
   in the panel's shape: an outline that arrives on hover, so a record being
   read still reads as text rather than as a form.
   min-height because an empty string renders as no text at all - without it
   the field would collapse to nothing and there would be no way to click into
   the one value most likely to need filling in. */
.row-detail-val-editable {
  cursor: text;
  min-height: 1.25em;
  border-radius: 3px;
}
.row-detail-val-editable:hover { outline: 1px solid rgba(76,194,255,0.4); outline-offset: 2px; }
.row-detail-val-editable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.row-detail-foot {
  flex: 0 0 auto;
  display: flex;
  gap: 8px;
  padding: 10px 14px;
  border-top: 1px solid var(--border);
}
.row-detail-foot .btn { flex: 1; justify-content: center; }

/* ==========================================================================
   status bar (28px)
   ========================================================================== */

.app-statusbar {
  flex: 0 0 28px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 14px;
  background: var(--panel);
  border-top: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--t3);
  gap: 16px;
  min-width: 0;
}

.statusbar-left {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.statusbar-right {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}

.statusbar-dot { color: var(--green); }
.statusbar-sep { color: var(--t3); }

/* ==========================================================================
   ⌘K command palette
   ========================================================================== */

.cmd-palette-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(4,7,12,.55);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 14vh;
}

.cmd-palette-box {
  width: min(620px, 92vw);
  background: var(--panel);
  border: 1px solid var(--bstrong);
  border-radius: 14px;
  box-shadow: var(--shadow);
  overflow: hidden;
}

.cmd-palette-search {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 14px 17px;
  border-bottom: 1px solid var(--border);
  color: var(--t3);
}

.cmd-palette-input {
  flex: 1;
  font: inherit;
  font-size: 14px;
  color: var(--text);
  background: transparent;
  border: 0;
  outline: 0;
}
.cmd-palette-input::placeholder { color: var(--t3); }

.cmd-palette-body { padding: 6px 0 8px; max-height: 380px; overflow-y: auto; }

.cmd-palette-section {
  padding: 6px 17px;
  font-size: 10.5px;
  letter-spacing: .08em;
  font-weight: 600;
  color: var(--t3);
}

.cmd-palette-item {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 17px;
  cursor: pointer;
  font-size: 13px;
  background: transparent;
  border: 0;
  width: 100%;
  text-align: left;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  transition: background .1s;
}
.cmd-palette-item:hover,
.cmd-palette-item.is-active { background: var(--panel2); }

.cmd-palette-tag {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 600;
  padding: 2px 6px;
  border-radius: 5px;
  white-space: nowrap;
  flex: 0 0 auto;
}

.cmd-palette-label { flex: 1; }

.cmd-palette-key {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--t3);
  flex: 0 0 auto;
}

.cmd-palette-foot {
  display: flex;
  gap: 12px;
  padding: 6px 17px;
  font-size: 10.5px;
  color: var(--t3);
  border-top: 1px solid var(--border);
}

/* ==========================================================================
   empty states
   ========================================================================== */

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  flex: 1 1 auto;
  padding: 26px 18px;
  text-align: center;
}
.empty-title { font-size: 13px; font-weight: 600; color: var(--t2); }
.empty-body { font-size: 12px; color: var(--t3); max-width: 46ch; line-height: 1.5; }
.empty-compact { padding: 14px 8px; align-items: flex-start; text-align: left; }

/* ==========================================================================
   schema / objects tree
   ========================================================================== */

.schema-content {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-height: 0;
  overflow-y: auto;
}

.schema-group { display: flex; flex-direction: column; gap: 4px; }
.schema-group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 2px 2px 2px 4px;
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--t3);
}
.schema-list { display: flex; flex-direction: column; gap: 1px; }

.schema-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 5px 8px;
  font: inherit;
  font-size: 12.5px;
  color: var(--t2);
  text-align: left;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.schema-item:hover { color: var(--accent); background: var(--asoft); border-color: var(--border); }
.schema-item .schema-icon { display: flex; color: var(--t3); flex: 0 0 auto; }
.schema-item:hover .schema-icon { color: var(--accent); }
.schema-item .schema-name {
  font-family: var(--font-mono);
  font-size: 12px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.schema-note { padding: 4px 4px 0; font-size: 11.5px; color: var(--t3); }
.schema-error {
  padding: 6px 8px;
  font-size: 11.5px;
  color: var(--danger-text);
  background: var(--danger-soft);
  border: 1px solid var(--danger-border);
  border-radius: var(--radius-sm);
  overflow-wrap: anywhere;
}
.schema-loading { display: flex; align-items: center; gap: 8px; padding: 8px 4px; font-size: 12px; color: var(--t3); }

/* ==========================================================================
   editor + history
   ========================================================================== */

.editor:not(.ed-input) {
  width: 100%;
  min-height: 118px;
  resize: vertical;
  padding: 11px 12px;
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.55;
  color: var(--input-text);
  background: var(--input-bg);
  border: 1px solid var(--bstrong);
  border-radius: var(--radius-sm);
  tab-size: 2;
}
.editor:not(.ed-input):focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--asoft);
}
.editor:not(.ed-input):disabled { opacity: .6; cursor: not-allowed; }
.editor:not(.ed-input)::placeholder { color: var(--t3); opacity: .75; }

.history {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel2);
  overflow: hidden;
}
.history-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 7px 10px;
  border-bottom: 1px solid var(--border);
}
.history-title {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--t3);
}
.history-list { max-height: 190px; overflow-y: auto; padding: 4px; }
.history-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 5px 8px;
  font: inherit;
  color: var(--t2);
  text-align: left;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.history-item:hover { color: var(--accent); background: var(--asoft); border-color: var(--border); }
.history-q {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 11.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.history-t { flex: 0 0 auto; font-size: 10.5px; color: var(--t3); }
.history-empty { padding: 10px; font-size: 12px; color: var(--t3); }

/* ==========================================================================
   results grid
   ========================================================================== */

.results-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
}

.grid-scroll {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  overflow: auto;
}

table.grid {
  border-collapse: separate;
  border-spacing: 0;
  /* gridColumnMetrics() writes --grid-w and a <colgroup>; fixed layout makes
     the browser take those as given instead of re-deciding from the content,
     which is what used to let a 43-character header lose to a three-digit id.
     max-content is the fallback for anything that renders a grid without
     measuring one. */
  table-layout: fixed;
  width: var(--grid-w, max-content);
  min-width: 100%;
  font-family: var(--font-mono);
  font-size: 12.5px;
}

.grid th {
  position: sticky;
  top: 0;
  z-index: 2;
  padding: 9px 12px;
  color: var(--t3);
  background: var(--grid-head);
  border-bottom: 1px solid var(--border);
  text-align: left;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .05em;
  white-space: nowrap;
}
/* The header's contents live in a flex row so the tail can be the only part
   that ellipsizes. A <div> inside a <th> is fine; making the <th> itself the
   flex container would take it out of the table layout. */
.grid th .th-name {
  display: flex;
  align-items: baseline;
  min-width: 0;
  font-family: var(--font-mono);
}

/* The prefix a column shares with its siblings, and the part that identifies
   it. Dimming the prefix is the whole point: CREDENTIAL_PROFILE_ is context,
   CA_CERT_PROFILE_MAP_ID is the name. */
.name-prefix {
  flex: 0 0 auto;
  color: var(--t3);
  font-weight: 400;
}

/* Only the tail truncates, so what is lost to a narrow column is the end of the
   name - never the front, which would leave every sibling looking identical. */
.name-tail {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--t2);
}

/* In the grid header the tail is explicitly --t2 against the prefix's --t3.
   Note which way this moves: .grid th paints the whole name --t3 today, so the
   identifying half of every header gets brighter here, not dimmer - the tightest
   case, light theme, goes from 2.7:1 to 5.6:1 on the part that has to be read. */
.grid th .name-tail { color: var(--t2); }

/* The tree keeps whatever colour its row is using - selected rows are accent,
   hovered rows are --text - and dims only the prefix against it. */
.tree-label .name-tail { color: inherit; }

.grid td {
  padding: 6px 12px;
  max-width: 460px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: top;
}

.grid tbody tr:nth-child(even) > td { background: var(--row-alt); }
.grid tbody tr:hover > td { background: var(--row-hover); }
.grid tbody tr:last-child > td { border-bottom: 0; }

.col-num {
  position: sticky;
  left: 0;
  z-index: 1;
  width: 1%;
  min-width: 46px;
  padding-right: 10px;
  color: var(--t3);
  background: var(--grid-head);
  border-right: 1px solid var(--border);
  text-align: right;
  font-size: 11px;
  user-select: none;
}
th.col-num { z-index: 3; }
.grid tbody tr:nth-child(even) > td.col-num { background: var(--grid-head); }
.grid tbody tr:hover > td.col-num { color: var(--accent); background: var(--grid-head); }

.cell-null { color: var(--t3); font-style: italic; opacity: .7; }
.cell-empty { min-width: 34px; }
.cell-number { text-align: right; font-variant-numeric: tabular-nums; }
.cell-bool { color: var(--green); }
.cell-json { color: var(--accent); }
.cell-clipped { cursor: help; }

/* Soaks up whatever min-width:100% adds beyond the measured widths, so the grid
   fills its pane without any real column being stretched past what it needs. */
.col-spacer { border-right: 0 !important; padding: 0 !important; }

.grid-note {
  padding: 8px 12px;
  font-size: 11.5px;
  color: var(--amber);
  background: var(--warn-soft);
  border-top: 1px solid var(--warn-border);
}

.grid-zero { padding: 18px 14px; color: var(--t3); font-size: 12.5px; text-align: center; }

.msg-result {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 16px;
  padding: 12px 14px;
  font-size: 13px;
  color: var(--text);
  background: var(--panel2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--green);
  border-radius: var(--radius-sm);
}
.msg-result .msg-icon { color: var(--green); display: flex; padding-top: 1px; flex: 0 0 auto; }
.msg-result .msg-text { white-space: pre-wrap; overflow-wrap: anywhere; }

.result-error {
  margin: 16px;
  padding: 12px 14px;
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--danger-text);
  background: var(--danger-soft);
  border: 1px solid var(--danger-border);
  border-radius: var(--radius-sm);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  overflow: auto;
}
.result-error-title {
  display: block;
  margin-bottom: 6px;
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--danger);
}

.results-status {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 6px 14px;
  border-top: 1px solid var(--border);
  background: var(--panel2);
  font-size: 11.5px;
  color: var(--t3);
  min-height: 32px;
}
.results-status:empty { display: none; }
.status-item { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.status-item + .status-item::before { content: '\00b7'; margin-right: 5px; color: var(--bstrong); }
.status-strong { color: var(--t2); font-weight: 600; font-variant-numeric: tabular-nums; }
.status-msg {
  color: var(--t2);
  max-width: 60ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.status-warn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  color: var(--amber);
  background: var(--warn-soft);
  border: 1px solid var(--warn-border);
  border-radius: var(--radius-sm);
  font-weight: 500;
}
.status-bad { color: var(--danger); font-weight: 600; }

/* ==========================================================================
   toasts
   ========================================================================== */

.toast-host {
  position: fixed;
  right: 16px;
  bottom: 44px; /* above status bar */
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  pointer-events: none;
}
.toast {
  max-width: 340px;
  padding: 8px 12px;
  font-size: 12.5px;
  color: var(--text);
  background: var(--panel3);
  border: 1px solid var(--bstrong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .18s ease, transform .18s ease;
  overflow-wrap: anywhere;
}
.toast-show { opacity: 1; transform: none; }

/* ==========================================================================
   object browser: tree
   ========================================================================== */

.tree {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.tree-node { min-width: 0; }
.tree-children { display: flex; flex-direction: column; gap: 1px; min-width: 0; }

.tree-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding-top: 3px;
  padding-bottom: 3px;
  padding-right: 6px;
  font-size: 12.5px;
  color: var(--t2);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: default;
  user-select: none;
}
.tree-row:hover { color: var(--text); background: var(--row-hover); }
.tree-row.is-selected { color: var(--accent); background: var(--asoft); border-color: var(--border); }
.tree-row.is-selected .tree-icon { color: var(--accent); }
.tree-row-catalog, .tree-row-dbgroup { font-weight: 600; color: var(--text); }
.tree-row-folder { color: var(--t3); }

.tree-twisty {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 14px; height: 14px;
  color: var(--t3);
}
.tree-twisty.is-toggle { cursor: pointer; }
.tree-twisty.is-toggle:hover { color: var(--accent); }
.tree-row.is-open > .tree-twisty { transform: rotate(90deg); }

.tree-icon { flex: 0 0 auto; display: flex; color: var(--t3); }
.tree-row:hover .tree-icon { color: var(--accent); }

.tree-label {
  flex: 0 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Split into prefix and tail only when there is a prefix worth dimming; the
   plain text case stays a text node and keeps ellipsizing as it always did. */
.tree-label.has-prefix {
  display: flex;
  align-items: baseline;
  overflow: visible;
}
.tree-row.is-selected .tree-label .name-prefix { color: var(--accent); opacity: .65; }
.tree-row-catalog .tree-label, .tree-row-dbgroup .tree-label, .tree-row-folder .tree-label {
  font-family: var(--font-sans);
  font-size: 12px;
  letter-spacing: .01em;
}

.tree-detail {
  flex: 0 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--t3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tree-meta {
  flex: 0 0 auto;
  margin-left: auto;
  padding-left: 6px;
  font-size: 10.5px;
  color: var(--t3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.tree-spinner { flex: 0 0 auto; width: 11px; height: 11px; margin-left: 4px; color: var(--accent); }

.badge-kind {
  flex: 0 0 auto;
  padding: 0 5px;
  font-size: 9.5px;
  font-weight: 600;
  line-height: 15px;
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--t3);
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.badge-kind-table       { color: var(--accent); background: var(--asoft); }
.badge-kind-view,
.badge-kind-matview     { color: var(--amber); background: var(--warn-soft); border-color: var(--warn-border); }
.badge-kind-collection,
.badge-kind-keyspace    { color: var(--green); }
.badge-kind-measurement,
.badge-kind-stream      { color: var(--t2); }
.badge-kind-driver,
.badge-kind-database    { color: var(--t3); background: transparent; }

.tree-mark {
  flex: 0 0 auto;
  padding: 0 4px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  line-height: 15px;
  white-space: nowrap;
  color: var(--t3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.tree-mark-pk      { color: var(--amber); background: var(--warn-soft); border-color: var(--warn-border); }
.tree-mark-uniq    { color: var(--accent); background: var(--asoft); }
.tree-mark-notnull { color: var(--t2); }
.tree-mark-null    { color: var(--t3); border-style: dashed; }
.tree-mark-default { color: var(--t3); max-width: 18ch; overflow: hidden; text-overflow: ellipsis; }
.tree-mark-freq    { color: var(--green); }
.tree-mark-rule    { color: var(--t3); }

.tree-message {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  padding-top: 3px;
  padding-bottom: 3px;
  padding-right: 6px;
  font-size: 11.5px;
}
.tree-message-text { min-width: 0; overflow-wrap: anywhere; }
.tree-loading   { color: var(--t3); }
.tree-note-row  { color: var(--t3); font-style: italic; }
.tree-error     { color: var(--danger-text); }
.tree-error .tree-message-text {
  padding: 2px 6px;
  background: var(--danger-soft);
  border: 1px solid var(--danger-border);
  border-radius: var(--radius-sm);
}

.schema-note-term { font-family: var(--font-mono); color: var(--t2); }

/* ==========================================================================
   context menu
   ========================================================================== */

.ctx-menu {
  position: fixed;
  z-index: 80;
  min-width: 186px;
  max-width: 280px;
  padding: 4px;
  background: var(--panel);
  border: 1px solid var(--bstrong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
}
.ctx-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 7px 6px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
  color: var(--t3);
}
.ctx-head-icon { display: flex; flex: 0 0 auto; }
.ctx-head-name {
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ctx-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 5px 8px;
  font: inherit;
  font-size: 12.5px;
  color: var(--text);
  text-align: left;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.ctx-item:hover, .ctx-item:focus-visible { color: var(--accent); background: var(--asoft); border-color: var(--border); }
.ctx-item-icon { display: flex; flex: 0 0 auto; color: var(--t3); }
.ctx-item:hover .ctx-item-icon, .ctx-item:focus-visible .ctx-item-icon { color: var(--accent); }
.ctx-item-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The one entry in this menu that destroys data. Colour is not the only carrier:
   the label says "Delete" and the confirmation names the key. */
.ctx-item-danger, .ctx-item-danger .ctx-item-icon { color: var(--danger-text); }
.ctx-item-danger:hover, .ctx-item-danger:focus-visible { color: var(--danger-text); background: var(--danger-soft); border-color: var(--danger-border); }
.ctx-item-danger:hover .ctx-item-icon, .ctx-item-danger:focus-visible .ctx-item-icon { color: var(--danger-text); }
.ctx-sep { height: 1px; margin: 4px 2px; background: var(--border); }

/* ==========================================================================
   modals (DDL, edit-review, transfer)
   ========================================================================== */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(4,7,12,.55);
  backdrop-filter: blur(3px);
}
body.theme-light .modal-backdrop { background: rgba(15,23,42,.34); }
body.theme-matrix .modal-backdrop { background: rgba(0,0,0,.82); }

.modal {
  display: flex;
  flex-direction: column;
  width: min(860px, 100%);
  max-height: 100%;
  min-height: 0;
  background: var(--panel);
  border: 1px solid var(--bstrong);
  border-radius: 14px;
  box-shadow: var(--shadow);
  overflow: hidden;
}

/* A one-question dialog. 860px of width around a single text field reads as a
   layout mistake rather than a prompt. */
.modal-narrow { width: min(420px, 100%); }
.modal-narrow .modal-body { display: grid; gap: 6px; }

.modal-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 14px;
  background: var(--panel2);
  border-bottom: 1px solid var(--border);
}
.modal-title {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
  font-size: 12.5px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--t3);
}
.modal-title .modal-subject {
  font-family: var(--font-mono);
  font-size: 12.5px;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.modal-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.modal-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 14px 14px;
  overflow: hidden;
}
.modal-note {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--t3);
  overflow-wrap: anywhere;
}
.ddl-text {
  flex: 1 1 auto;
  min-height: 120px;
  margin: 0;
  padding: 10px 12px;
  font-size: 12px;
  line-height: 1.55;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: auto;
  white-space: pre;
  tab-size: 4;
}

/* ==========================================================================
   collapsible panels
   ========================================================================== */

.panel-twisty {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; flex: 0 0 auto;
  padding: 0; margin-right: 2px;
  color: var(--t3); background: none; border: 0; cursor: pointer;
  border-radius: 3px;
  transition: transform .12s ease, color .12s ease;
  transform: rotate(90deg);
}
.panel-twisty:hover { color: var(--accent); }
.panel-twisty:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.panel.is-collapsed .panel-twisty { transform: rotate(0deg); }
.panel.is-collapsed { flex: 0 0 auto !important; min-height: 0 !important; }
.panel.is-collapsed .panel-header { border-bottom: 0; }
.panel.is-collapsed > *:not(.panel-header) { display: none !important; }

.tree-row.is-active > .tree-label { color: var(--accent); font-weight: 600; }
.tree-row.is-active > .tree-icon { color: var(--accent); }
.tree-row.is-active::after {
  content: "in use";
  margin-left: auto;
  padding: 0 5px;
  font-size: .62rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 3px;
  opacity: .85;
}

/* ==========================================================================
   row editing
   ========================================================================== */

.edit-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 6px 16px;
  border-bottom: 1px solid var(--border);
  background: rgba(148,163,184,0.05);
}
.edit-bar[hidden] { display: none; }
.edit-hint, .edit-why { font-size: .76rem; color: var(--t3); }
.edit-why { font-style: italic; }
.edit-ro { border: 1px solid var(--border); }
.edit-ro.is-on { color: var(--amber); border-color: var(--amber); font-weight: 600; }

.cell-editable { cursor: text; }
.cell-editable:hover { outline: 1px solid rgba(76,194,255,0.4); outline-offset: -1px; }
.cell-editable:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.cell-pending { background: rgba(214,160,59,.16); box-shadow: inset 2px 0 0 var(--amber); }
.cell-input {
  width: 100%; box-sizing: border-box;
  padding: 1px 3px; margin: -2px 0;
  font: inherit; font-family: var(--font-mono);
  color: var(--input-text); background: var(--input-bg);
  border: 1px solid var(--accent); border-radius: 2px; outline: 0;
}
/* The gutter holds the row number and, while editing, the delete button. */
.col-num-edit {
  min-width: 74px;
  padding-right: 4px;
  white-space: nowrap;
}
.col-num-edit .rownum { margin-right: 4px; }

/* The row number is the handle that opens the row-detail panel, so it is a real
   button - reachable by keyboard, announced as a control - styled to stay the
   quiet gutter figure it always looked like. */
.rownum {
  padding: 0 2px;
  border: 0;
  border-radius: 3px;
  background: none;
  color: inherit;
  font: inherit;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.rownum:hover { color: var(--accent); }
.rownum:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The row the detail panel is showing. Marked on the row rather than on its
   cells so it survives a horizontal scroll. */
.grid tbody tr.row-open > td { background: var(--asoft); }
.grid tbody tr.row-open > td.col-num {
  background: var(--asoft);
  color: var(--accent);
  font-weight: 700;
}

.row-del {
  width: 22px; height: 22px;
  padding: 0;
  vertical-align: middle;
  color: var(--t2);
  /* Always shown. This used to be opacity:0 until the row was hovered, which
     made the only way to delete a row undiscoverable: nothing on screen said
     the control was there, it never appeared at all on a touch screen, and it
     was invisible to anyone who does not move a mouse across the grid first.
     Keeping it quiet is the muted --t3 colour's job; hiding it outright was
     doing that work twice. Stray clicks are not the risk they would be either,
     because this only MARKS the row - Apply asks again before anything is
     deleted. */
  opacity: 1;
}
/* The icon has to be legible, not merely present. At the shared 14px default
   the trash renders with a ~1.2px stroke in a 22px gutter and reads as three
   grey dots next to the row number - "1 …", "2 …" - which is not recognisable
   as a control at all. Bigger and heavier, sized here rather than by passing a
   size through iconButton() so the grid's own control can be tuned without
   changing every other icon in the app. */
.row-del svg {
  width: 16px; height: 16px;
  stroke-width: 2.1;
}
.grid tbody tr:hover .row-del,
.row-del:focus-visible,
.row-del.is-marked { opacity: 1; }
.row-del:hover { color: var(--danger); border-color: var(--danger-border); }
.row-del.is-marked { color: var(--danger); }

tr.row-deleted .cell { text-decoration: line-through; opacity: .5; }
tr.row-deleted .col-num { color: var(--red); font-weight: 700; }

.edit-review { margin: 0; padding: 0 0 0 4px; list-style: none; max-height: 320px; overflow-y: auto; }
.edit-review-line {
  padding: 5px 8px; margin-bottom: 4px;
  font-size: .78rem; white-space: pre-wrap; word-break: break-word;
  color: var(--input-text);
  background: rgba(148,163,184,0.07);
  border-left: 2px solid var(--accent);
  border-radius: 3px;
}
.edit-review-note { margin: 10px 0 0; font-size: .76rem; color: var(--t3); }
.modal-foot { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--border); }
.edit-why { font-style: normal; color: var(--amber); }
.edit-keynote {
  padding: 1px 6px;
  font-size: .68rem; letter-spacing: .02em;
  color: var(--t2); cursor: help;
  border: 1px dashed var(--bstrong); border-radius: 3px;
}

/* ==========================================================================
   Transfer dialog
   ========================================================================== */

.modal-wide { width: min(980px, 100%); }

.ttabs {
  display: flex; gap: 2px;
  padding: 0 14px;
  border-bottom: 1px solid var(--border);
  background: var(--panel2);
  flex: 0 0 auto;
}
.ttab {
  padding: 9px 14px;
  font-size: 12.5px; font-weight: 500;
  color: var(--t2); background: none;
  border: 0; border-bottom: 2px solid transparent;
  cursor: pointer;
}
.ttab:hover { color: var(--text); }
.ttab.is-active { color: var(--accent); border-bottom-color: var(--accent); }
.ttab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.transfer-body { display: flex; flex-direction: column; gap: 18px; overflow-y: auto; }

.tsection { display: flex; flex-direction: column; gap: 8px; }
.tsection-title { margin: 0; font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--t3); }
.tfield { display: flex; flex-direction: column; gap: 5px; }
.tfield-label { font-size: 12px; font-weight: 600; color: var(--t2); }
.tfield-hint { margin: 0; font-size: 11.5px; color: var(--t3); line-height: 1.45; }
.tcheck { display: flex; align-items: baseline; gap: 8px; font-size: 13px; cursor: pointer; }
.tcheck input { margin: 0; flex: 0 0 auto; }
.tcheck-hint { color: var(--t3); font-size: 11.5px; }
.tradios { display: flex; flex-direction: column; gap: 6px; }
.tradio {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 7px 9px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-size: 13px; cursor: pointer;
}
.tradio:hover { border-color: var(--bstrong); }
.tradio input { margin: 2px 0 0; flex: 0 0 auto; }
.tradio-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tradio-label { font-weight: 600; }
.tradio-hint { font-size: 11.5px; color: var(--t3); line-height: 1.45; }
.tradio.is-danger .tradio-label { color: var(--danger); }
.tpick-tools { display: flex; gap: 6px; }
.tpick-list {
  max-height: 220px; overflow: auto;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 4px;
}
.tpick-row { display: flex; align-items: center; gap: 8px; padding: 3px 6px; font-size: 12.5px; cursor: pointer; border-radius: 3px; }
.tpick-row:hover { background: var(--asoft); }
.tpick-row input { margin: 0; flex: 0 0 auto; }
.tbanner {
  display: flex; gap: 8px; align-items: flex-start;
  padding: 9px 11px; border-radius: var(--radius-sm);
  font-size: 12.5px; line-height: 1.5;
  border: 1px solid var(--bstrong);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.tbanner-warn { border-color: var(--amber); color: var(--amber); }
.tbanner-error { border-color: var(--danger); color: var(--danger); }
.tplan-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.tplan { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.tplan th { text-align: left; padding: 5px 8px; font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--t3); border-bottom: 1px solid var(--border); }
.tplan td { padding: 5px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
.tplan-exists { color: var(--t3); }
.tplan tr.is-destructive td { color: var(--danger); }
.tplan-flag { margin-left: 7px; padding: 1px 6px; border-radius: 999px; font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; border: 1px solid currentColor; }
.tplan tr.tplan-issue td { color: var(--danger); font-size: 11.5px; padding-top: 0; border-bottom: 1px solid var(--border); }
.tjob {
  display: flex; flex-direction: column; gap: 5px; align-items: flex-start;
  padding: 10px 12px; border: 1px solid var(--border); border-left-width: 3px; border-radius: var(--radius-sm);
}
.tjob-running   { border-left-color: var(--accent); }
.tjob-done      { border-left-color: var(--green); }
.tjob-failed    { border-left-color: var(--danger); }
.tjob-cancelled { border-left-color: var(--t3); }
.tjob-head { display: flex; align-items: baseline; gap: 10px; width: 100%; }
.tjob-label { font-size: 13px; font-weight: 600; }
.tjob-state { margin-left: auto; font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--t3); }
.tjob-running .tjob-state { color: var(--accent); }
.tjob-failed .tjob-state { color: var(--danger); }
.tjob-progress, .tjob-result { font-size: 12px; color: var(--t2); }
.tjob-warn { font-size: 11.5px; color: var(--amber); }
.tjob-error { font-size: 12px; color: var(--danger); }
.transfer-note { margin-right: auto; font-size: 11.5px; color: var(--t3); }
#transfer-btn.is-busy { color: var(--accent); border-color: var(--accent); }
.tconfirm {
  display: flex; flex-direction: column; gap: 10px;
  padding: 16px 18px; border: 1px solid var(--danger); border-radius: var(--radius-sm);
}
.tconfirm-title { margin: 0; font-size: 13.5px; font-weight: 700; color: var(--danger); }
.tconfirm-list { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 2px; font-size: 12.5px; }
.tconfirm-note { margin: 0; font-size: 12px; color: var(--t2); }
.tconfirm-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 4px; }
.btn-danger { color: #fff; background: var(--danger); border: 1px solid var(--danger); }
.btn-danger:hover { filter: brightness(1.08); }
.tradio.is-disabled { opacity: .55; cursor: not-allowed; }
.tradio.is-disabled .tradio-hint { color: var(--amber); }
.tcode {
  margin: 0; padding: 9px 11px;
  font-family: var(--font-mono); font-size: 11.5px; line-height: 1.5;
  color: var(--text); background: var(--panel2);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  white-space: pre; overflow-x: auto;
}
.tdirset {
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 12px; border: 1px dashed var(--bstrong); border-radius: var(--radius-sm);
}
.tdirset-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tdirset-label { font-size: 12px; color: var(--t2); }
.tdirset-path { font-size: 12px; color: var(--text); word-break: break-all; }
.tdirset-row .btn { margin-left: auto; }
.tdirset-title { margin: 0; font-size: 12.5px; font-weight: 700; }
.tdirset-actions { display: flex; gap: 8px; justify-content: flex-end; }

/* Picking a dump already on the server: the select takes the room, Refresh keeps
   its natural width beside it. */
.tfilepick-row { display: flex; align-items: center; gap: 8px; }
.tfilepick-row select { flex: 1 1 auto; min-width: 0; }

/* Job progress. The width is set from real counters by app-transfer.js; the
   transition only smooths the step between two polls. */
.tprog { display: flex; align-items: center; gap: 8px; margin: 6px 0 2px; }
.tprog-track {
  position: relative;
  flex: 1 1 auto;
  height: 6px;
  overflow: hidden;
  background: var(--panel3);
  border-radius: 999px;
}
.tprog-fill {
  height: 100%;
  background: var(--accent);
  border-radius: inherit;
  transition: width .3s linear;
}
.tprog-done .tprog-fill      { background: var(--ok); }
.tprog-failed .tprog-fill    { background: var(--danger); }
.tprog-cancelled .tprog-fill { background: var(--warn); }
.tprog-pct {
  flex: 0 0 auto;
  min-width: 34px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--t2);
}

/* Indeterminate: the job is running but has no object total to divide by yet.
   A moving stripe rather than a filled bar, so it never reads as "n% done". */
.tprog-track.is-indeterminate .tprog-fill {
  background: linear-gradient(90deg,
    transparent 0%, var(--accent) 50%, transparent 100%);
  opacity: .7;
  transition: none;
  animation: tprog-sweep 1.2s linear infinite;
}
@keyframes tprog-sweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}

/* ==========================================================================
   Editor (phase 2)
   ALIGNMENT CONTRACT: font-family, font-size, line-height, padding, tab-size
   are set INLINE by app-editor.js from SqlEditor.METRICS. Do NOT set them here.
   ========================================================================== */

.ed-host {
  display: flex;
  align-items: stretch;
  width: 100%;
  /* autoGrowEditor() in app-panels.js writes --editor-h. It is deliberately not
     writing `height`: dragging the resize gripper sets an inline height, which
     outranks this rule, so a size someone chose by hand is never overwritten by
     the next keystroke. The fallback is the three-row resting height. */
  height: var(--editor-h, 82px);
  min-height: 62px;
  resize: vertical;
  overflow: hidden;
  background: var(--input-bg);
  border: 1px solid var(--bstrong);
  border-radius: var(--radius-sm);
}
.ed-host:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--asoft);
}
.ed-gutter {
  flex: 0 0 auto;
  overflow: hidden;
  text-align: right;
  user-select: none;
  color: var(--t3);
  background: rgba(148,163,184,0.06);
  border-right: 1px solid var(--border);
  padding-right: 8px;
  padding-left: 8px;
  will-change: transform;
}
.ed-lineno { opacity: .65; }
.ed-scroller { position: relative; flex: 1 1 auto; min-width: 0; overflow: hidden; }
.ed-highlight {
  position: absolute;
  top: 0; left: 0;
  margin: 0;
  white-space: pre;
  pointer-events: none;
  will-change: transform;
  border: 0;
  background: none;
}
.ed-highlight code { font: inherit; white-space: pre; }
.ed-input.editor {
  position: relative;
  z-index: 2;
  display: block;
  width: 100%; height: 100%;
  min-height: 0;
  margin: 0;
  overflow: auto;
  white-space: pre;
  overflow-wrap: normal;
  color: transparent;
  -webkit-text-fill-color: transparent;
  caret-color: var(--input-text);
  background: transparent;
  border: 0; border-radius: 0; outline: 0; resize: none;
}
.ed-input.editor::selection { background: rgba(76,194,255,.34); }
.ed-input.editor::placeholder { color: var(--t3); -webkit-text-fill-color: var(--t3); opacity: .75; }
.ed-brackets { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 1; will-change: transform; }
.ed-bracket { position: absolute; border-radius: 2px; background: rgba(76,194,255,.30); outline: 1px solid rgba(76,194,255,.60); }

/* token colours: dark */
.tk-keyword       { color: #7dd3fc; font-weight: 600; }
.tk-type          { color: #c4b5fd; }
.tk-function      { color: #fbbf24; }
.tk-string        { color: #86efac; }
.tk-number        { color: #fca5a5; }
.tk-line-comment,
.tk-block-comment { color: #64748b; font-style: italic; }
.tk-ident-quoted  { color: #f0abfc; }
.tk-param         { color: #f472b6; }
.tk-operator      { color: #94a3b8; }
.tk-punct         { color: #8ea0b5; }
.tk-ident         { color: var(--input-text); }
.tk-unterminated  { text-decoration: underline wavy #f87171; text-underline-offset: 3px; }

/* token colours: light */
body.theme-light .tk-keyword       { color: #0369a1; }
body.theme-light .tk-type          { color: #6d28d9; }
body.theme-light .tk-function      { color: #b45309; }
body.theme-light .tk-string        { color: #15803d; }
body.theme-light .tk-number        { color: #b91c1c; }
body.theme-light .tk-line-comment,
body.theme-light .tk-block-comment { color: #64748b; }
body.theme-light .tk-ident-quoted  { color: #a21caf; }
body.theme-light .tk-param         { color: #be185d; }
body.theme-light .tk-operator      { color: #475569; }
body.theme-light .tk-punct         { color: #64748b; }
body.theme-light .ed-gutter        { background: rgba(15,23,42,.04); }

/* token colours: matrix */
body.theme-matrix .ed-host,
body.theme-matrix .ac-popup,
body.theme-matrix .findbar,
body.theme-matrix .buftab { border-radius: 0; }
body.theme-matrix .ed-gutter        { background: #001100; }
body.theme-matrix .tk-keyword       { color: #00ff00; font-weight: 700; }
body.theme-matrix .tk-type          { color: #00e000; }
body.theme-matrix .tk-function      { color: #7cff7c; }
body.theme-matrix .tk-string        { color: #00cc00; }
body.theme-matrix .tk-number        { color: #b6ffb6; }
body.theme-matrix .tk-line-comment,
body.theme-matrix .tk-block-comment { color: #008800; }
body.theme-matrix .tk-ident-quoted  { color: #66ff66; }
body.theme-matrix .tk-param         { color: #9dff9d; }
body.theme-matrix .tk-operator,
body.theme-matrix .tk-punct         { color: #00aa00; }
body.theme-matrix .tk-ident         { color: #00ff00; }

/* completion popup */
.ac-popup {
  position: fixed;
  z-index: 80;
  min-width: 240px; max-width: 460px;
  background: var(--panel3);
  border: 1px solid var(--bstrong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.ac-list { max-height: 260px; overflow-y: auto; margin: 0; padding: 4px; list-style: none; }
.ac-item { display: flex; align-items: baseline; gap: 8px; padding: 5px 10px; border-radius: 4px; cursor: pointer; font-size: .82rem; white-space: nowrap; }
.ac-item:hover { background: var(--asoft); }
.ac-item.ac-active { background: var(--asoft); }
.ac-label { font-family: var(--font-mono); color: var(--input-text); }
.ac-match { font-weight: 700; color: var(--accent); }
.ac-detail, .ac-scope { color: var(--t3); font-size: .75rem; }
.ac-kind { margin-left: auto; padding: 0 5px; border-radius: 3px; font-size: .64rem; letter-spacing: .04em; text-transform: uppercase; color: var(--t3); border: 1px solid var(--border); }
.ac-pk { color: var(--amber); font-size: .68rem; }
.ac-foot { padding: 4px 10px; border-top: 1px solid var(--border); color: var(--t3); font-size: .7rem; display: flex; gap: 12px; }
.ac-pending { opacity: .6; }
body.theme-matrix .ac-popup { background: #000; box-shadow: none; }
body.theme-matrix .ac-item.ac-active { background: #003300; }
body.theme-matrix .ac-label, body.theme-matrix .ac-match { color: #00ff00; }

/* find / replace bar */
.findbar { margin-bottom: 8px; padding: 6px 8px; background: rgba(148,163,184,.07); border: 1px solid var(--border); border-radius: var(--radius-sm); }
.findbar[hidden] { display: none; }
.findbar-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.findbar-row + .findbar-row { margin-top: 6px; }
.findbar-icon { color: var(--t3); display: inline-flex; }
.findbar-input { font-family: var(--font-mono); min-width: 150px; }
.findbar-count { color: var(--t3); font-size: .76rem; min-width: 62px; }
.findbar-hint { color: #f87171; font-size: .76rem; }
.findbar-nav { color: var(--t2); }
.findbar-nav:hover { color: var(--accent); }
.findbar-toggle { padding: 2px 6px; font-size: .72rem; cursor: pointer; user-select: none; color: var(--t2); background: transparent; border: 1px solid var(--border); border-radius: 4px; }
.findbar-toggle-on, .findbar-toggle[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); font-weight: 600; }
.ed-findhits { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 1; }
.ed-findhit { position: absolute; border-radius: 2px; background: rgba(214,160,59,.34); }
.ed-findhit-current { background: rgba(214,160,59,.62); }
body.theme-matrix .findbar { border-radius: 0; background: #001100; }
body.theme-matrix .findbar-toggle { border-radius: 0; }

/* buffer tabs */
.buftabs { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; margin-bottom: 6px; }
.buftabs-empty { color: var(--t3); font-size: .74rem; }
.buftab { display: inline-flex; align-items: center; gap: 6px; padding: 3px 8px; font-size: .76rem; cursor: pointer; color: var(--t2); background: transparent; border: 1px solid var(--border); border-radius: var(--radius-sm); }
.buftab:hover { color: var(--input-text); }
.buftab-active, .buftab.active, .buftab[aria-selected="true"] { color: var(--input-text); border-color: var(--db-color, var(--accent)); background: var(--asoft); }
.buftabs[class*="db-"] .buftab-active { background: color-mix(in srgb, var(--db-color) 14%, transparent); }
/* Names come from the connection now, so they can be long. */
.buftab-label { font-family: var(--font-mono); max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.buftab-dirty { color: var(--amber); }
.buftab-close { opacity: .6; }
.buftab-close:hover { opacity: 1; color: #f87171; }
.buftabs-add { color: var(--t2); }
.buftabs-add:hover { color: var(--accent); }
body.theme-matrix .buftab, body.theme-matrix .buftabs-add { border-radius: 0; }
body.theme-matrix .buftab-active, body.theme-matrix .buftab.active { background: #003300; }

/* fold markers */
.ed-fold-marker { cursor: pointer; color: var(--t3); }
.ed-fold-marker:hover { color: var(--accent); }
.fold-outline { max-height: 200px; overflow-y: auto; }
.fold-outline-item { display: flex; align-items: baseline; gap: 8px; width: 100%; padding: 3px 8px; font-size: .78rem; text-align: left; background: none; border: 0; cursor: pointer; color: var(--input-text); }
.fold-outline-item:hover { background: var(--asoft); }
.fold-outline-line { color: var(--t3); font-family: var(--font-mono); font-size: .72rem; }
.fold-outline-label { font-family: var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fold-outline-meta, .fold-outline-verb { color: var(--t3); font-size: .72rem; }
.findbar-toggles { display: inline-flex; gap: 4px; }
.findbar-input-bad { border-color: #f87171; }
.findbar-replace { font-family: var(--font-mono); min-width: 150px; }
.findbar-expand-on { color: var(--accent); }
.findbar-action { white-space: nowrap; }
.buftab-main { display: inline-flex; align-items: center; gap: 6px; background: none; border: 0; color: inherit; cursor: pointer; font: inherit; }
.buftab-dot { color: var(--t3); }
.buftabs-open, .buftabs-save { color: var(--t2); }
.buftabs-open:hover, .buftabs-save:hover { color: var(--accent); }

/* ==========================================================================
   matrix theme overrides
   ========================================================================== */

body.theme-matrix .tree-row.is-selected { background: var(--asoft); border-color: var(--border); }
body.theme-matrix .badge-kind,
body.theme-matrix .tree-mark { border-color: var(--t2); }
body.theme-matrix .badge-kind-table { border-color: var(--border); }
body.theme-matrix .ctx-menu,
body.theme-matrix .modal { box-shadow: none; }
body.theme-matrix .tree-note-row { font-style: normal; }
body.theme-matrix .ddl-text { color: var(--text); }
body.theme-matrix .tradio,
body.theme-matrix .tpick-list,
body.theme-matrix .tbanner,
body.theme-matrix .tjob { border-radius: 0; }
body.theme-matrix .tconfirm { border-radius: 0; }
body.theme-matrix .tcode { border-radius: 0; }
body.theme-matrix .tdirset { border-radius: 0; }
body.theme-matrix .tree-row.is-active::after { border-radius: 0; }
body.theme-matrix .cell-pending { background: #003300; box-shadow: inset 2px 0 0 #00ff00; }
body.theme-matrix .cell-input { border-radius: 0; }
body.theme-matrix .edit-ro.is-on { color: #00ff00; border-color: #00ff00; }
body.theme-matrix .edit-review-line { border-radius: 0; background: #001100; border-left-color: #00ff00; }
body.theme-matrix .edit-why { color: #00ff00; }
body.theme-matrix .edit-keynote { border-radius: 0; color: #00aa00; }
body.theme-matrix .app-rail,
body.theme-matrix .sidebar { border-radius: 0; }
body.theme-matrix .cmd-palette-box,
body.theme-matrix .conn-card { border-radius: 0; }

/* ==========================================================================
   responsive
   ========================================================================== */

@media (max-width: 1000px) {
  /* Stacked: the rail turns into a bar across the top, so the flyout comes down
     from under it instead of out from beside it. Same three states, same class
     on .app-body - only the axis changes.

     The offsets read --rail-h, which syncRailHeight() in app-boot.js measures
     off the rail itself. The 44px fallback is its flex-basis here, but that is
     only ever a lower bound: a flex item's automatic minimum size beats
     flex-basis, so the rail is as tall as its buttons and padding make it. */
  .app { height: auto; min-height: 100vh; }
  .app-body { flex-direction: column; }
  .app-rail {
    flex-direction: row; width: 100%; flex: 0 0 44px;
    border-right: 0; border-bottom: 1px solid var(--border);
  }
  .rail-spacer { flex: 0; }

  .sidebar {
    left: 0;
    top: var(--rail-h, 44px);
    bottom: auto;
    width: 100%;
    max-height: 320px;
    border-right: 0;
    border-bottom: 1px solid var(--border);
    transform: translateY(-100%);
    transition: transform .16s ease, visibility 0s linear .16s;
  }
  .app-body.sidebar-open .sidebar { transform: none; }
  .sidebar-backdrop { inset: var(--rail-h, 44px) 0 0 0; }

  /* Nothing to drag: the flyout is the full width of the screen. */
  .sidebar-resizer { display: none; }

  .panel-results { min-height: 420px; }

  /* The row-detail panel stops being a 320px column beside the grid and becomes
     a drawer under it. It used to be display:none here, which was fine while
     nothing opened it - now that a row number does, hiding it would leave a
     control that visibly promises something and delivers nothing. */
  .results-area { flex-direction: column; }
  .row-detail {
    flex: 0 0 auto;
    max-height: 45vh;
    border-left: 0;
    border-top: 1px solid var(--border);
  }
}

/* Phone: the results grid stacks into one card per row.
   A result set is arbitrarily wide -- five columns of a PERSON table are
   already unreadable at 390px and the sticky header scrolls away from the
   values it names. Each cell prints its own column name instead (data-col,
   set in app-results.js) and the <thead> is dropped. The gutter row number
   becomes the card's heading. Matches the 760px breakpoint the panel shell
   uses, so the grid changes shape at the same width the rest of the app does. */
@media (max-width: 760px) {
  .grid-scroll { overflow-x: hidden; }
  table.grid { width: 100%; min-width: 0; display: block; table-layout: auto; }
  table.grid colgroup { display: none; }
  /* No table shape left to fill, so the spacer is just an empty card row. */
  table.grid .col-spacer { display: none; }
  table.grid thead { display: none; }
  table.grid tbody { display: block; }
  table.grid tbody tr {
    display: block;
    border: 1px solid var(--border); border-radius: 10px;
    margin: 0 0 8px; padding: 4px 0; background: var(--panel);
  }
  table.grid tbody tr:hover > td { background: none; }
  table.grid tbody tr:nth-child(even) > td { background: none; }
  table.grid td {
    display: block; max-width: none; width: auto;
    white-space: normal; overflow: visible; text-overflow: clip;
    word-break: break-word; border-bottom: 0; padding: 5px 12px;
  }
  table.grid td::before {
    content: attr(data-col);
    display: block; font-size: 10px; letter-spacing: .06em;
    color: var(--t3); text-transform: uppercase; margin-bottom: 1px;
  }
  table.grid td.col-num {
    position: static; width: auto; min-width: 0; text-align: left;
    border-right: 0; border-bottom: 1px solid var(--border);
    padding: 6px 12px; margin-bottom: 2px;
  }
  table.grid td.col-num::before { content: none; }
  table.grid tbody tr:nth-child(even) > td.col-num,
  table.grid tbody tr:hover > td.col-num { background: var(--grid-head); }
  table.grid td.grid-zero::before { content: none; }
  /* An empty cell has nothing to size it once the table shape is gone. */
  table.grid td.cell-empty { min-height: 16px; }
}

@media (max-width: 640px) {
  .app-topbar { padding: 0 10px; gap: 8px; }
  .topbar-brand-name { display: none; }
  .topbar-breadcrumb { display: none; }
  .topbar-search { display: none; }
  .palette-kbd { display: none; }
  .kbd-group { display: none; }
  .topbar-actions { gap: 6px; }
  .health-latency { display: none; }
  .theme-select { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: 1.6s !important;
  }
}

@media print {
  .app-topbar, .app-rail, .sidebar, .tabs, .panel-editor,
  .toast-host, .panel-header-actions, .app-statusbar { display: none !important; }
  .app, .app-body, .main, .panel-results { height: auto; overflow: visible; }
  .grid-scroll { overflow: visible; }
  .ctx-menu, .modal-backdrop, .cmd-palette-backdrop { display: none !important; }
}

/* ==========================================================================
   saved queries + settings sidebar views
   ========================================================================== */

.saved-list { display: flex; flex-direction: column; gap: 4px; overflow: auto; min-height: 0; }

.saved-item {
  display: flex;
  align-items: stretch;
  gap: 2px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
}
.saved-item:hover { background: var(--panel2); border-color: var(--border); }

/* The row is a button so the whole card opens the query; the actions sit outside
   it, because a button inside a button is not valid and does not receive clicks. */
.saved-item-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 6px 8px;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
}
.saved-item-name {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.saved-item-meta {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--t3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.saved-item-actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 2px;
  padding-right: 4px;
  opacity: 0;
}
.saved-item:hover .saved-item-actions,
.saved-item:focus-within .saved-item-actions { opacity: 1; }
.saved-item-delete:hover { color: #f87171; }

.sidebar-empty {
  margin: 6px 2px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--t3);
}
.sidebar-empty-note { color: var(--t3); opacity: .8; }

.settings-content {
  display: flex;
  flex-direction: column;
  gap: 14px;
  overflow: auto;
  min-height: 0;
}

.settings-group { display: flex; flex-direction: column; gap: 6px; }
.settings-group-title {
  margin: 0;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--t3);
}

.settings-row { display: grid; gap: 3px; }
.settings-row-label { font-size: 12px; color: var(--t2); }
.settings-row-value { font-size: 12px; color: var(--text); word-break: break-all; }
.settings-row-hint { font-size: 10.5px; line-height: 1.45; color: var(--t3); }

/* ---------- Backups (settings section + dialog); see app-backups.js ---------- */

/* One configured backup in the sidebar. A card rather than a table row: the
   sidebar is 290px and the four facts each backup has - schedule, connection,
   contents, outcome - do not fit across. */
.bk-row {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 6px 7px;
  margin-top: 4px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--panel2);
}
.bk-row-top { display: flex; align-items: baseline; gap: 6px; }
.bk-name {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bk-tag {
  flex: 0 0 auto;
  font-size: 9.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--t3);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0 4px;
}
.bk-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 10.5px;
  line-height: 1.45;
  color: var(--t3);
}
.bk-error {
  font-size: 10.5px;
  line-height: 1.4;
  color: var(--danger);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bk-actions { display: flex; gap: 2px; margin-top: 2px; }

/* The cron preview inside the dialog. */
.bk-preview {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: baseline;
  font-size: 11.5px;
  color: var(--t2);
}
.bk-preview-label { color: var(--t3); }
.bk-preview-time {
  font-variant-numeric: tabular-nums;
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0 5px;
}
.bk-preview-error { color: var(--danger); font-size: 11.5px; }

/* The source-schema tick list in the transfer dialog. A column rather than a
   row: schema names vary in length, and a wrapping row of checkboxes puts the
   labels at unpredictable places. */
.tschemas { display: flex; flex-direction: column; gap: 2px; }
