/* web-api-listener admin UI — compact, themeable, light/dark via design tokens. */
:root {
  color-scheme: light dark;
  --bg: #f6f7f9;
  --surface: #ffffff;
  --surface-2: #eef1f5;
  --border: #d7dce3;
  --text: #1c2330;
  --muted: #5b6675;
  --accent: #2b6cb0;
  --accent-text: #ffffff;
  --danger: #b02a37;
  --warning: #9a6400;
  --radius: 8px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #11151c; --surface: #1a212b; --surface-2: #232c38; --border: #313c4b;
    --text: #e7ecf3; --muted: #9aa6b4; --accent: #5aa2e6; --accent-text: #0b0f14;
    --warning: #e0b23d;
  }
}

* { box-sizing: border-box; }
html, body { height: 100%; }
/* App shell: body is a full-height flex column; the topnav is a fixed-height
   band and the content area fills the rest. The PAGE never scrolls — individual
   panes own their scroll (desktop pattern; relaxed to normal flow < 900px). */
body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--font);
  font-size: 14px; line-height: 1.45; display: flex; flex-direction: column; overflow: hidden; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.topnav { position: relative; flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: .35rem .85rem; align-items: center;
  padding: .6rem 6.5rem .6rem 1rem; background: var(--surface); border-bottom: 1px solid var(--border);
  transition: background-color .2s ease, border-color .2s ease; }
.topnav .nav-sep { color: var(--muted); }

/* Prod is a different color on purpose — a loud, unmissable cue that every
   click on this bar (including Dispatch buttons) now acts against real data. */
.topnav-prod { background: var(--danger); border-bottom-color: color-mix(in srgb, var(--danger) 55%, black); }
.topnav-prod a { color: #fff; }
.topnav-prod .nav-sep { color: rgba(255, 255, 255, .65); }
.topnav-prod .env-indicator { background: rgba(255, 255, 255, .16); border-color: rgba(255, 255, 255, .5); color: #fff; }
.topnav-prod .env-indicator:hover { background: rgba(255, 255, 255, .28); }
.env-menu { position: fixed; top: .5rem; right: 1rem; z-index: 100; }
.env-indicator { font: inherit; color: var(--text); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--radius); padding: .3rem .75rem; cursor: pointer; }
.env-indicator:hover { background: var(--border); }
/* The panel is a centered modal (like .dispatch-modal-backdrop elsewhere in
   this stylesheet) rather than a small dropdown anchored under the button. */
.env-modal-backdrop { position: fixed; inset: 0; z-index: 200; display: flex;
  align-items: center; justify-content: center; padding: 1rem; background: rgba(0,0,0,.45); }
.env-modal-backdrop[hidden] { display: none; }
.env-menu-panel { width: min(420px, 100%); background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: 0 18px 45px rgba(0,0,0,.3); padding: 1.5rem; }
.env-menu-current { font-size: 15px; color: var(--muted); margin-bottom: 1.1rem; }
.env-menu-current strong { color: var(--text); }

/* Test/Prod switch — track shows both labels; the thumb slides under whichever
   one is active, matching the topnav's accent + surface tokens (light/dark). */
.env-toggle-wrapper { display: flex; justify-content: center; position: relative; border-radius: .55em;
  padding: .15em; background: var(--surface-2); box-shadow: inset 0 1px 2px rgba(0,0,0,.2);
  font-size: 1.7em; }
.env-toggle-checkbox { appearance: none; position: absolute; inset: 0; z-index: 2;
  margin: 0; border-radius: inherit; font: inherit; opacity: 0; cursor: pointer; }
.env-toggle-track { position: relative; display: block; width: 8.5em; height: 2em;
  border-radius: .4em; background: var(--bg); overflow: hidden;
  box-shadow: inset 0 0 .0625em .125em rgba(255,255,255,.04), inset 0 .0625em .125em rgba(0,0,0,.35); }
.env-toggle-thumb { position: absolute; top: .15em; left: .15em; z-index: 0;
  width: calc(50% - .2em); height: calc(100% - .3em); border-radius: .3em;
  background: var(--accent); box-shadow: 0 .125em .25em rgba(0,0,0,.35);
  transition: left .25s ease; }
.env-toggle-checkbox:checked ~ .env-toggle-track .env-toggle-thumb { left: calc(50% + .05em); }
.env-toggle-labels { position: relative; z-index: 1; display: grid; grid-template-columns: 1fr 1fr;
  width: 100%; height: 100%; }
.env-toggle-labels span { display: flex; align-items: center; justify-content: center;
  font-size: .68em; font-weight: 700; letter-spacing: .05em; color: var(--muted);
  transition: color .25s ease; }
.env-toggle-checkbox:not(:checked) ~ .env-toggle-track .env-toggle-labels span:first-child,
.env-toggle-checkbox:checked ~ .env-toggle-track .env-toggle-labels span:last-child { color: var(--accent-text); }

.env-menu-actions { display: flex; gap: .75rem; margin-top: 1.25rem; }
.env-btn { flex: 1; font: inherit; font-size: 14.5px; color: var(--text); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--radius); padding: .6rem .75rem; cursor: pointer; }
.env-btn:hover { background: var(--border); }
.env-btn-save { color: var(--accent-text); background: var(--accent); border-color: var(--accent); }
.env-btn-save:hover { filter: brightness(1.08); }

.env-transition { display: flex; align-items: center; justify-content: center; gap: .75rem; margin: 0 0 1.1rem; }
.env-pill { padding: .45rem 1rem; border-radius: 999px; font-size: 13px; font-weight: 700;
  letter-spacing: .04em; border: 1px solid var(--border); background: var(--surface-2); color: var(--muted); }
.env-pill-to { color: var(--accent-text); background: var(--accent); border-color: var(--accent); }
.env-transition-arrow { width: 1.4rem; height: 1.4rem; flex: 0 0 auto; color: var(--muted); }

.env-confirm-message { margin: 0 0 1.25rem; font-size: 15px; color: var(--text); line-height: 1.45; }
.env-confirm-btn { flex: 1; font: inherit; font-size: 14.5px; font-weight: 600; color: var(--text);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius);
  padding: .6rem .75rem; cursor: pointer; transition: background-color .15s ease, border-color .15s ease, color .15s ease; }
.env-confirm-yes:hover { background: color-mix(in srgb, #1f9d55 22%, var(--surface-2)); border-color: #1f9d55; color: #1f9d55; }
.env-confirm-no:hover { background: color-mix(in srgb, var(--danger) 22%, var(--surface-2)); border-color: var(--danger); color: var(--danger); }

/* Reports-in-Prod notice (admin/list.html) — report_* tables aren't part of
   dsp_metadata_prod yet. */
.entity-unavailable-notice { margin: 0 0 1rem; padding: .85rem 1rem; border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, var(--warning) 45%, var(--border));
  background: color-mix(in srgb, var(--warning) 14%, var(--surface)); color: var(--text); font-size: 13.5px; line-height: 1.5; }

/* Content area fills the viewport below the nav and hides its own overflow;
   full-height list/card pages manage scroll internally. Simple pages (home,
   login, utilities, admin tables) get an inner scroll via .content > *. */
.content { flex: 1 1 auto; min-height: 0; padding: 1rem; overflow: hidden;
  display: flex; flex-direction: column; }
.breadcrumbs {
  flex: 0 0 auto; color: var(--muted); margin-bottom: .75rem; font-size: 13px;
  display: flex; align-items: center; gap: .4rem; min-height: 1.2rem;
}
.breadcrumbs a { color: var(--muted); }
.breadcrumbs .separator { color: color-mix(in srgb, var(--muted) 65%, transparent); }

/* Generic card surface used by list panes and hand-written pages. Cards are
   inline-size containers so content (e.g. action chips) can adapt to the
   card's width rather than the viewport's. */
.card { background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: 0 1px 3px rgba(0,0,0,.06);
  display: flex; flex-direction: column; min-height: 0; overflow: hidden;
  container-type: inline-size; }
@media (prefers-color-scheme: dark) { .card { box-shadow: 0 1px 3px rgba(0,0,0,.35); } }

h1 { font-size: 1.35rem; margin: .2rem 0 1rem; }
h2 { font-size: 1.05rem; margin: 1.5rem 0 .6rem; }

.list-header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }

/* Card header row (title + Add new), old-site style: small uppercase bold title
   with a bottom border separating it from the toolbar/table below. */
.card-header { display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: .6rem .75rem; border-bottom: 1px solid var(--border); }
.card-header h1 { margin: 0; font-size: .85rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .03em; color: var(--text); }
.header-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: .35rem; }
.header-actions form { margin: 0; }

table { width: 100%; border-collapse: collapse; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
th, td { text-align: left; padding: .45rem .6rem; border-bottom: 1px solid var(--border); }
th { background: var(--surface-2); font-weight: 600; font-size: 12.5px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .02em; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--surface-2); }
tr.inactive td { opacity: .55; }
th.actions { white-space: nowrap; text-align: right; }
/* Action cells: a right-aligned flex row of compact chips. One line when the
   column is wide enough; wraps into tidy right-aligned lines (uniform gap, no
   ragged per-chip margins) when it isn't. Inline delete/toggle forms contribute
   their button directly to the row via display:contents. */
td.actions { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center;
  align-content: center; gap: .25rem; }
td.actions form { display: contents; }
td.actions .btn-sm { margin: 0; padding: .15rem .45rem; font-size: 12px; white-space: nowrap; }
td.actions a.row-link:hover { background: var(--surface); text-decoration: none; }
/* Once the containing card is wide enough for a full line of chips, keep them
   on one line like the old site (the 1/3 list card crosses this near ~1450px
   viewports; full-width cards are always past it). */
@container (min-width: 460px) {
  td.actions { flex-wrap: nowrap; }
}
/* Client-apps list: keep the four action chips on ONE fully-visible line at the
   narrow 1/3-width card. The Application column is the flexible shrink point --
   it wraps to more lines so the action row never gets pushed off the card and
   clipped by .card-table-scroll's overflow-x: hidden. */
.client-apps-list td.actions { flex-wrap: nowrap; }
.client-apps-list .col-application { width: 100%; word-break: break-word; }
/* --- Draggable pane split (see base.html) -----------------------------------
   Sits in the flex flow between the list and detail cards; the handle itself is
   injected by script, so a page with only one pane simply never gets one. Wide
   enough to grab comfortably, with a thin visible line so it reads as a divider
   rather than a gap. */
.pane-resizer { flex: 0 0 10px; align-self: stretch; position: relative;
  cursor: col-resize; touch-action: none; border-radius: 3px; }
.pane-resizer::before { content: ""; position: absolute; top: 0; bottom: 0; left: 4px;
  width: 2px; border-radius: 2px; background: var(--border); transition: background .12s; }
.pane-resizer:hover::before, .pane-resizer:focus-visible::before { background: var(--accent); }
.pane-resizer:focus-visible { outline: none; }
/* While dragging, keep the col-resize cursor even as the pointer leaves the
   handle, and stop the drag from selecting text across the page. */
body.pane-resizing { cursor: col-resize; user-select: none; }
/* Nothing to divide when the list is collapsed to its rail, or when the panes
   stack into normal document flow on narrow screens. */
.entity-panes.list-collapsed .pane-resizer { display: none; }

/* --- Registry list tables (admin/list.html) ---------------------------------
   The generic engine renders a variable number of display columns, so widths
   cannot be hard-coded per page the way .jobs-table does — they are computed
   per entity in the template and land here as inline percentages.
   table-layout:fixed then makes the columns share the card's width instead of
   sizing to content and overflowing it: Reports has five display columns and
   its Actions cell was clipped clean off the card by .card-table-scroll's
   overflow-x:hidden. Percentages rather than rem because this card is a
   1/3-width flex pane — see the .jobs-table note below for what fixed widths
   do when they sum past the card. */
.registry-list table.card-table { table-layout: fixed; width: 100%; }
/* Long unbroken values (report and integration names) wrap inside their share
   instead of forcing the column wider. */
.registry-list table.card-table td:not(.actions) { overflow-wrap: anywhere; }
/* Ids and badges are short fixed-shape values: never break mid-token, and at a
   narrow card do not stack their few characters one per line — let the cell clip
   instead, the treatment .jobs-table gives its id/seq/active columns. */
.registry-list td.col-id, .registry-list td.col-active {
  overflow-wrap: normal; white-space: nowrap; }
/* Stacked action chips, as on the scheduling pages: one per line, so a row's
   action count never widens the table. Overrides the `@container
   (min-width: 460px)` nowrap rule above. */
.registry-list td.actions { flex-wrap: wrap; justify-content: flex-end; min-width: 0; }
.registry-list td.actions .btn-sm {
  flex: 0 1 100%; min-width: 0; text-align: center;
  white-space: normal; line-height: 1.15; }

.filter-note { color: var(--muted); font-size: 13px; margin: .25rem 0 0; }

/* Card table: borderless (the card provides the frame), compact cells, sticky
   header, lives inside a flex-grow scroll region so only the body scrolls. */
.card-table-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; }
table.card-table { border: 0; border-radius: 0; }
table.card-table th, table.card-table td { padding: .4rem .6rem; font-size: 12.5px; }
table.card-table thead th { position: sticky; top: 0; z-index: 1; }

.search { display: flex; gap: .5rem; align-items: center; margin: .75rem 0; }
input, select, textarea, button { font: inherit; color: var(--text); }
input, select, textarea { background: var(--bg); border: 1px solid #000;
  border-radius: 6px; padding: .35rem .5rem; }
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: 0; }

button, .btn { background: var(--accent); color: var(--accent-text); border: 0;
  border-radius: 6px; padding: .4rem .7rem; cursor: pointer; font-weight: 600; }
button:hover, .btn:hover { filter: brightness(1.05); text-decoration: none; }

form.form label, .admin-settings label, .admin-users label, section.form label {
  display: block; margin: .6rem 0; }
/* Plain (non-.field) form labels stack their control below the text — but not
   the toolbar's inline show-inactive toggle, whose checkbox must stay inline. */
form label:not(.field):not(.toggle-inactive) input,
form label:not(.field):not(.toggle-inactive) select {
  display: block; margin-top: .2rem; min-width: 280px; }

.errors { color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, transparent);
  border: 1px solid var(--danger); border-radius: 6px; padding: .5rem .75rem; list-style: none; }
.reveal { background: var(--surface-2); border: 1px solid var(--accent); border-radius: 6px;
  padding: .6rem .8rem; margin: .75rem 0; }
.reveal code { display: inline-block; margin-top: .25rem; user-select: all; word-break: break-all; }
.dispatch-alert {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: .75rem;
  margin: .65rem .75rem;
  padding: .65rem .8rem;
  border-radius: 6px;
  border: 1px solid var(--border);
}
.dispatch-alert strong { display: block; font-size: 13px; }
.dispatch-alert span { color: var(--muted); font-size: 12.5px; }
.dispatch-alert code { user-select: all; overflow-wrap: anywhere; font-size: 12px; }
.dispatch-alert-success {
  border-color: color-mix(in srgb, #2fa45a 65%, var(--border));
  background: color-mix(in srgb, #2fa45a 12%, var(--surface));
  animation: dispatch-queued-pulse 2s ease-in-out infinite;
}
@keyframes dispatch-queued-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, #2fa45a 45%, transparent); }
  50% { box-shadow: 0 0 0 6px color-mix(in srgb, #2fa45a 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .dispatch-alert-success { animation: none; }
}
.dispatch-alert-running {
  border-color: color-mix(in srgb, var(--accent) 65%, var(--border));
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  animation: dispatch-queued-pulse 2s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .dispatch-alert-running { animation: none; }
}
/* Terminal success: same colors as the in-flight banner, no pulse -- the run
   is done, so nothing about the banner should still look "in progress". */
.dispatch-alert-done {
  border-color: color-mix(in srgb, #2fa45a 65%, var(--border));
  background: color-mix(in srgb, #2fa45a 12%, var(--surface));
}
[data-queued-timer] { display: block; margin-top: .1rem; font-variant-numeric: tabular-nums; }
.dispatch-alert-error {
  grid-template-columns: auto minmax(0, 1fr);
  border-color: color-mix(in srgb, var(--danger) 65%, var(--border));
  background: color-mix(in srgb, var(--danger) 12%, var(--surface));
}
.dispatch-alert-error strong { color: var(--danger); }
.hint { color: var(--muted); font-size: 12.5px; }
.hint .warn { display: block; color: var(--warning); font-weight: 600; margin-top: .2rem; }

.login { max-width: 340px; margin: 3rem auto; }

/* --- Forms: sections, fields, badges (parity with reference UI) --- */
.id-badge { background: var(--surface-2); border: 1px solid var(--border); color: var(--muted);
  border-radius: 6px; padding: .25rem .55rem; font-size: 12.5px; }
h2.form-section { font-size: .8rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); margin: 1rem 0 .3rem; border-bottom: 1px solid var(--border); padding-bottom: .25rem; }
h2.form-section:first-child { margin-top: .2rem; }
label.field { display: block; margin: .35rem 0; }
.field-label { display: block; margin-bottom: .15rem; }
.field-label .req { color: var(--danger); font-style: normal; }
label.field input, label.field select, label.field textarea {
  display: block; width: 100%; }
label.field textarea { min-height: 2.4rem; resize: vertical; font-family: inherit; }
/* Structured-config fields (JSON run params, header blocks, URL param values)
   opt into monospace via FormField(mono=True) so brackets and key alignment
   read cleanly. Prose fields (notes) keep the proportional body face. The
   font stack matches .adv-json so config reads the same everywhere. */
label.field textarea.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px; line-height: 1.45; }
/* Focus expander (see base.html): a popup that stands in for a single-line
   input whose value overflows it. Anchored to <body> and positioned in
   viewport coordinates by JS, so it never participates in form layout — the
   field underneath keeps its size and the page does not reflow on click.
   Only the chrome and the entrance animation live here. */
.field-expand {
  position: fixed; z-index: 60; box-sizing: border-box; display: block;
  margin: 0; padding: .35rem .5rem; resize: none; overflow: hidden;
  line-height: 1.4; color: var(--text); background: var(--surface-2);
  border: 1px solid var(--accent); border-radius: var(--radius);
  box-shadow: 0 10px 28px rgb(0 0 0 / .45); outline: none;
  /* Grows out of the input's own top-left corner rather than fading in from
     nowhere, so the popup reads as the field expanding. */
  transform-origin: top left; opacity: 0; transform: scale(.94);
  transition: opacity .13s ease-out, transform .13s cubic-bezier(.2, .8, .3, 1.15); }
.field-expand.is-open { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .field-expand { transition: none; opacity: 1; transform: none; } }
.form-actions { margin-top: 1rem; display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }
.form-actions .meta { margin-left: auto; color: var(--muted); font-size: 12px; }
.form-actions a.cancel { color: var(--muted); }

.badge { display: inline-block; padding: .08rem .45rem; border-radius: 999px; font-size: 12px;
  border: 1px solid transparent; }
.badge.on { color: #14702f; background: color-mix(in srgb, #2fa45a 16%, transparent); border-color: #2fa45a; }
.badge.off { color: var(--muted); background: var(--surface-2); border-color: var(--border); }
.badge.badge-all { color: #7a3d00; background: color-mix(in srgb, var(--warning) 20%, transparent); border-color: var(--warning); font-weight: 700; }
@media (prefers-color-scheme: dark) { .badge.on { color: #74e39b; } .badge.badge-all { color: var(--warning); } }

/* --- API key scopes (prototype, admin/api_keys.html) ---
   Hierarchy: "all" (top-level chip) > section chip (e.g. "crg") > leaf chips
   (e.g. "crg:read") nested under their section. Checking a parent
   auto-checks + disables everything beneath it (wired in the page script). */
.scope-badges { display: flex; flex-wrap: wrap; gap: .25rem; margin-bottom: .35rem; }
.scope-editor-row td { background: var(--surface-2); padding: .75rem .9rem; }
.scope-editor { display: flex; flex-direction: column; gap: .6rem; }
.scope-editor-actions { display: flex; align-items: center; gap: .6rem; }

/* --- API key management --- */
.api-keys-body { display: flex; flex-direction: column; gap: 1rem; }
.api-management-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(340px, 440px);
  gap: 1rem;
  align-items: start;
}
.api-panel { min-width: 0; overflow: hidden; }
.api-side-panel {
  border-left: 1px solid var(--border);
  padding-left: 1rem;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}
.section-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.api-key-list { display: grid; gap: .75rem; }
.api-key-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: .75rem;
}
.api-key-card.inactive { opacity: .72; }
.api-key-summary {
  display: flex;
  justify-content: space-between;
  gap: .75rem;
  align-items: flex-start;
}
.api-key-title { display: flex; align-items: flex-start; gap: .6rem; min-width: 0; }
.api-key-title h3 { margin: 0; font-size: 1rem; overflow-wrap: anywhere; }
.meta-line { color: var(--muted); font-size: 12.5px; margin-top: .1rem; }
.api-key-status { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: .25rem; }
.api-key-secret { display: grid; gap: .25rem; margin: .55rem 0 .35rem; }
.api-key-secret input {
  width: 100%;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
}
.api-key-editor { margin-top: .65rem; }
.api-key-editor > summary {
  list-style: none;
  width: max-content;
  margin: .15rem 0 .6rem;
}
.api-key-editor > summary::-webkit-details-marker { display: none; }
.api-key-form, .stacked-form { display: flex; flex-direction: column; gap: .75rem; min-width: 0; }
.api-form-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(150px, 1fr)) auto;
  gap: .65rem;
  align-items: end;
}
.api-form-grid label, .stacked-form label { margin: 0; min-width: 0; }
.api-form-grid input, .api-form-grid select,
.stacked-form input, .stacked-form select {
  display: block;
  width: 100%;
  min-width: 0;
  margin-top: .2rem;
}
.permissions-block { display: flex; flex-direction: column; gap: .35rem; }
.permission-picker { display: grid; gap: .45rem; max-width: 100%; }
.permission-option {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: .75rem;
  min-height: 34px;
  padding: .35rem .55rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  font-size: 12.5px;
  font-weight: 600;
}
.permission-option input {
  width: 16px;
  height: 16px;
  min-width: 16px;
  margin: 0;
}
.permission-option-all { color: var(--warning); border-color: color-mix(in srgb, var(--warning) 45%, var(--border)); }
.permission-group {
  display: grid;
  gap: .35rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: .45rem;
  background: var(--surface-2);
}
.permission-children {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: .35rem;
}
.permission-option-leaf { font-weight: 500; }
.api-key-danger { margin-top: .5rem; }
.api-user-list { display: grid; gap: .5rem; }
.api-user-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: .5rem;
  align-items: center;
}
.api-user-card input[name="user_name"] { min-width: 0; width: 100%; }
.api-user-card .checkbox-row { grid-column: 1 / 2; }
.api-user-card .btn-sm { grid-column: 2 / 3; justify-self: end; }
.empty-panel {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  color: var(--muted);
  padding: .9rem;
}
.checkbox-row {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  white-space: nowrap;
}
@media (max-width: 1100px) {
  .api-management-layout { grid-template-columns: 1fr; }
  .api-side-panel { border-left: 0; border-top: 1px solid var(--border); padding: 1rem 0 0; }
}
@media (max-width: 760px) {
  .api-key-summary { flex-direction: column; }
  .api-key-status { justify-content: flex-start; }
  .api-form-grid, .api-user-card { grid-template-columns: 1fr; }
  .api-user-card .checkbox-row, .api-user-card .btn-sm { grid-column: auto; justify-self: start; }
}

a.btn-sm, .btn-sm { display: inline-block; background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px; padding: .2rem .55rem; font-size: 12.5px;
  font-weight: 600; margin-left: .35rem; }
a.btn-sm:hover { text-decoration: none; border-color: var(--accent); }
a.btn-sm.row-link { color: var(--accent); }
button.btn-sm { cursor: pointer; }
button:disabled, button.btn-sm:disabled {
  opacity: .62;
  cursor: progress;
  filter: none;
}
button.btn-sm.danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 50%, var(--border)); }

.dispatch-modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgba(0, 0, 0, .38);
}
.dispatch-modal-backdrop[hidden] { display: none; }
.dispatch-modal {
  width: min(460px, 100%);
  max-height: min(86vh, 520px);
  overflow: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 18px 45px rgba(0,0,0,.22);
}
.dispatch-modal header,
.dispatch-modal footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  padding: .7rem .85rem;
  border-bottom: 1px solid var(--border);
}
.dispatch-modal footer {
  justify-content: flex-end;
  border-top: 1px solid var(--border);
  border-bottom: 0;
}
.dispatch-modal h2 {
  margin: 0;
  font-size: .9rem;
  text-transform: uppercase;
  letter-spacing: .03em;
}
.dispatch-modal-close {
  width: 28px;
  height: 28px;
  padding: 0;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--muted);
  border: 1px solid var(--border);
}
.dispatch-modal-body { padding: .75rem .85rem; }
.dispatch-modal dl {
  display: grid;
  gap: .55rem;
  margin: 0;
}
.dispatch-modal dl div {
  display: grid;
  grid-template-columns: 7rem minmax(0, 1fr);
  gap: .65rem;
}
.dispatch-modal dt {
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .03em;
}
.dispatch-modal dd {
  margin: 0;
  overflow-wrap: anywhere;
}
.dispatch-modal .hint { margin: .75rem 0 0; }

/* ==========================================================================
   Run history (/history) — paginated batch runs + per-run job detail.
   Layout is the standard .entity-panes split; everything below is the run-state
   vocabulary (status chips, roll-up tiles, key/value grids, pager) that page adds.
   ========================================================================== */

/* Run-state chips. .badge supplies the pill shape; these supply the semantics:
   green = done, red = failed, blue = in flight, amber = mixed, grey = queued. */
.badge.status-success { color: #14702f; background: color-mix(in srgb, #2fa45a 16%, transparent); border-color: #2fa45a; }
.badge.status-failed  { color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, transparent); border-color: var(--danger); }
.badge.status-running { color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); border-color: var(--accent); }
.badge.status-partial { color: #7a3d00; background: color-mix(in srgb, var(--warning) 20%, transparent); border-color: var(--warning); }
.badge.status-pending,
.badge.status-unknown { color: var(--muted); background: var(--surface-2); border-color: var(--border); }
@media (prefers-color-scheme: dark) {
  .badge.status-success { color: #74e39b; }
  .badge.status-failed { color: #f28b95; }
  .badge.status-partial { color: var(--warning); }
}

/* The history list is denser than a config list: two lines per cell (primary
   value + dimmed context) instead of one column per attribute. */
.history-panes .entity-main { flex: 1.2 1 0; }
/* #run-list / #jobs-rows are just hx-swap-oob target wrappers -- neither is a
   flex child of .card by default, so .card-table-scroll's flex:1 (meant to
   grow the table region to fill the card, giving it a bounded height to
   scroll within) had no parent flex context to grow into. On /history that
   left a dead gap above the pager; on /jobs it left the table unbounded, so
   rows past the card's height were simply unreachable -- .card's
   overflow:hidden clipped them with no scrollbar. */
#run-list, #jobs-rows { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.history-table td { vertical-align: top; }
/* Zebra striping: a lighter tint than the hover shade so hovering a striped
   row still reads as a distinct state on top of it. */
.history-table tbody tr:nth-child(even), .run-jobs tbody tr:nth-child(even) {
  background: color-mix(in srgb, var(--surface-2) 55%, var(--surface));
}
.history-table tbody tr:hover, .run-jobs tbody tr:hover { background: var(--surface-2); }
/* Two-line cell: primary value over dimmed context. The stacking lives on the
   children, NOT on the td — a display:flex td drops out of the table's column
   layout and shunts the following cells onto their own lines. */
.run-cell > * { display: block; }
.run-name { font-weight: 600; }
.run-sub { color: var(--muted); font-size: 11.5px; line-height: 1.3; }
.tally { font-weight: 600; }
.tally-failed { color: var(--danger); }
.tally-running { color: var(--accent); }
.tally-ok { color: var(--muted); font-weight: 400; }
.empty-cell { color: var(--muted); padding: 1.25rem .6rem; }
tr.run-row:hover { cursor: default; }
/* Manual refresh (the page no longer polls): a labelled button in the list
   header and in the detail header. htmx puts .htmx-request on the button while
   its request is in flight, which spins the icon. */
.btn-refresh { display: inline-flex; align-items: center; gap: .3rem; }
@keyframes refresh-spin { to { transform: rotate(360deg); } }
.btn-refresh .refresh-icon { display: inline-block; }
.btn-refresh.htmx-request .refresh-icon { animation: refresh-spin .8s linear infinite; }
.btn-refresh.htmx-request { pointer-events: none; opacity: .7; }
tr.row-failed td { background: color-mix(in srgb, var(--danger) 6%, transparent); }

/* Pager: fixed footer band on the list card so page links stay put while the
   table scrolls. Numbered pages with elision (Google/Amazon style). */
.pager { flex: 0 0 auto; display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: .25rem; padding: .55rem .75rem; border-top: 1px solid var(--border); background: var(--surface-2); }
.pager a, .pager span { font-size: 12.5px; line-height: 1; }
.pager-page, .pager-step { display: inline-block; min-width: 1.75rem; text-align: center;
  padding: .3rem .5rem; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--accent); }
.pager-page:hover, .pager-step:hover { border-color: var(--accent); text-decoration: none; }
.pager-page.current { background: var(--accent); border-color: var(--accent); color: var(--accent-text); font-weight: 700; }
.pager-step.disabled { color: var(--muted); background: var(--surface-2); border-style: dashed; }
.pager-gap { padding: .3rem .2rem; color: var(--muted); }

/* Detail pane: header + roll-up tiles + summary grid + job table. */
.run-detail { display: flex; flex-direction: column; gap: .9rem; }
.run-detail-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.run-detail-title { display: flex; align-items: center; gap: .5rem; margin: 0; font-size: 1.05rem; }
.run-detail-head .hint { margin: .2rem 0 0; }
.run-section-head { margin: 0 0 .4rem; font-size: .78rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--muted); }

/* Roll-up tiles: the five numbers an operator scans before reading any table. */
.run-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: .5rem; }
.run-stat { display: flex; flex-direction: column; gap: .1rem; padding: .55rem .7rem;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface-2); }
.run-stat-value { font-size: 1.25rem; font-weight: 700; line-height: 1.1; }
.run-stat-label { font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.run-stat.ok .run-stat-value { color: #14702f; }
.run-stat.bad { border-color: color-mix(in srgb, var(--danger) 55%, var(--border)); }
.run-stat.bad .run-stat-value { color: var(--danger); }
.run-stat.busy { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.run-stat.busy .run-stat-value { color: var(--accent); }
@media (prefers-color-scheme: dark) { .run-stat.ok .run-stat-value { color: #74e39b; } }

/* Label/value grid, used for both the batch_log summary and the modal body
   (Advanced popup's field grid and ETL section both extend this via .adv-grid). */
.kv-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: .55rem .9rem; margin: 0 -.4rem; }
.kv-row { display: grid; grid-template-columns: 8.5rem minmax(0, 1fr); gap: .5rem; align-items: baseline;
  padding: .3rem .4rem .4rem; border-bottom: 1px solid var(--border); border-radius: 4px; }
.kv-row:last-child { padding-bottom: .3rem; border-bottom: 0; }
/* Zebra tint by field order, same treatment as the run-history tables. The
   grid wraps into multiple columns, so this bands by DOM position rather
   than visual row — a checkerboard-by-field look, not clean horizontal
   stripes, since a fixed-column assumption would break at other container
   widths. */
.kv-row:nth-child(even) { background: color-mix(in srgb, var(--surface-2) 55%, var(--surface)); }
.kv-row dt { color: var(--muted); font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; }
.kv-row dd { margin: 0; font-size: 12.5px; overflow-wrap: anywhere; }
.kv-row dd.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; }

.run-jobs-scroll { border: 1px solid var(--border); border-radius: var(--radius); }
table.run-jobs { border: 0; }
table.run-jobs td { vertical-align: top; }
table.run-jobs .col-seq { width: 3rem; color: var(--muted); }
table.run-jobs .col-num { text-align: right; font-variant-numeric: tabular-nums; }
table.run-jobs .attempt { display: block; margin-top: .15rem; }
table.run-jobs th.actions, table.run-jobs td.actions { width: 6rem; }

/* Advanced modal: same shell as the dispatch modal, wider and scrollable since
   it carries full run parameters / extra_info payloads. */
.advanced-modal { width: min(680px, 100%); max-height: min(88vh, 720px); }
.advanced-modal .dispatch-modal-body { display: flex; flex-direction: column; gap: .9rem; }
.adv-grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.adv-section { display: flex; flex-direction: column; }
.adv-error { margin: 0; padding: .5rem .7rem; border-radius: 6px; font-size: 12.5px;
  color: var(--danger); border: 1px solid var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent); overflow-wrap: anywhere; }
.adv-error[hidden] { display: none; }
.adv-json { margin: 0; padding: .55rem .7rem; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface-2); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px; line-height: 1.4; max-height: 14rem; overflow: auto; }

@media (max-width: 900px) {
  .run-detail-head { flex-direction: column; }
  .kv-row { grid-template-columns: 1fr; gap: 0; }
}

/* --- Client schedule cards --- */
.batch-card { border: 1px solid var(--border); border-radius: var(--radius); margin: .9rem 0;
  background: var(--surface); overflow: hidden; }
.batch-card.inactive { opacity: .7; }
.batch-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .6rem .8rem; background: var(--surface-2); border-bottom: 1px solid var(--border); }
.batch-head .hint { margin-left: .5rem; }
.batch-actions { display: flex; align-items: center; gap: .6rem; }
.batch-actions form { display: inline; }
.batch-card table { border: 0; border-radius: 0; }

/* --- Schedules: batches grouped by client --- */
.scheduling-panes .entity-main { flex: 1.35 1 0; }
.scheduling-list-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: .75rem 1rem 1rem; }
.scheduling-panes table.card-table { table-layout: fixed; width: 100%; }
.scheduling-panes .card-table-scroll { overflow-x: hidden; }
.scheduling-panes th.actions { width: 5.75rem; }
.scheduling-panes td.actions {
  flex-wrap: wrap;
  justify-content: flex-end;
  min-width: 0;
}
.scheduling-panes td.actions .btn-sm {
  flex: 0 1 100%;
  min-width: 0;
  text-align: center;
  white-space: normal;
  line-height: 1.15;
}
.scheduling-panes table.card-table td:not(.actions) {
  overflow-wrap: anywhere;
}
/* Jobs table: every column gets a percentage width (they sum to 100%) instead
   of a mix of rem + auto -- rem widths on top of the fixed last-run/actions
   columns previously summed to more than the card's actual pixel width,
   which made table-layout:fixed overflow the card and .card-table-scroll's
   overflow-x:hidden clipped job_name and actions right off the page.
   job_id/seq/active are short, fixed-shape values so they get just enough to
   stay on one line; job_name gets the largest share since it's the one
   column expected to hold long free-text. */
.jobs-table th:nth-child(1), .jobs-table td:nth-child(1) { width: 8%; white-space: nowrap; overflow-wrap: normal; }
.jobs-table th:nth-child(2), .jobs-table td:nth-child(2) { width: 23%; }
.jobs-table th:nth-child(3), .jobs-table td:nth-child(3) { width: 11%; overflow-wrap: normal; }
.jobs-table th:nth-child(4), .jobs-table td:nth-child(4) { width: 15%; }
.jobs-table th:nth-child(5), .jobs-table td:nth-child(5) { width: 6%; white-space: nowrap; overflow-wrap: normal; }
.jobs-table th:nth-child(6), .jobs-table td:nth-child(6) { width: 8%; white-space: nowrap; overflow-wrap: normal; }
.jobs-table th.last-run { width: 15%; white-space: nowrap; }
.jobs-table td.last-run { width: 15%; }
/* Actions td is display:flex (see td.actions above), which -- unlike a plain
   table-cell -- doesn't reliably inherit the table's fixed column width when
   it also carries its own conflicting `width`. Declaring the width on th
   only still fixes the column (table-layout:fixed derives column widths from
   the header row and applies them to every cell in the column); adding it to
   td too made Chrome shrink the flex container to ~0 and force-wrap every
   button's text down to a couple characters per line. */
.jobs-table th.actions { width: 14%; }
.client-group { margin: 0 0 1.1rem; }
.client-group-head { display: flex; align-items: baseline; gap: .5rem; margin: 1.1rem 0 .35rem;
  padding-bottom: .25rem; border-bottom: 1px solid var(--border); }
.client-group-head:first-child { margin-top: .2rem; }
/* One sticky header per client group would stack oddly while scrolling, so the
   grouped tables keep static headers (the client heading is the section marker). */
.client-group table.card-table thead th { position: static; }
.inline-table-scroll { overflow-x: hidden; overflow-y: visible; }
.inline-empty { min-height: 12rem; display: flex; flex-direction: column; justify-content: center; }

/* --- Sample data: horizontal scroll for wide views --- */
.table-scroll { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); }
.table-scroll table { border: 0; }

/* --- Full-height single-card pages (hand-written list/form pages) ---
   A .page-card fills the content area, carries a breadcrumb + header band, and
   scrolls its body internally so the page itself never scrolls. */
.page-card { flex: 1 1 auto; min-height: 0; }
.page-card .card-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: .75rem 1rem 1rem; }
/* Tables inside a card body: sticky header, borderless (card frames it). */
.page-card .card-body table { border: 0; border-radius: 0; }
.page-card .card-body > table thead th,
.page-card .card-body .table-scroll thead th { position: sticky; top: 0; z-index: 1; }
.page-card .card-body .table-scroll { border: 0; }
/* Form-style page cards get a comfortable reading width for the form body. */
.page-card.form-card .card-body { max-width: 1100px; }
/* Simple content pages (home, login) shouldn't be forced into a stretched card;
   let them size to content and scroll the content area if needed. */
.content > .home, .content > .login { flex: 0 0 auto; overflow-y: auto; }
.content > section:not(.page-card):not(.list-grid) { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

/* --- Utilities grid --- */
.util-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1rem; margin-top: 1rem; }
.util-card { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface);
  padding: .9rem 1rem; display: flex; flex-direction: column; gap: .4rem; }
.util-card h3 { margin: 0; font-size: 1rem; }
.util-card .btn { align-self: flex-start; margin-top: auto; }
.util-card.disabled { opacity: .6; }
.btn.disabled { background: var(--surface-2); color: var(--muted); cursor: not-allowed; border: 1px solid var(--border); }

.header-actions { display: flex; gap: .5rem; align-items: center; }

/* --- Item picker (single select scoped server-side to the client's vault) --- */
.item-picker { display: flex; gap: .5rem; flex-wrap: wrap; }
.item-picker .item-select { flex: 1 1 280px; }

/* --- Standard list toolbar: search + show/hide-inactive toggle --- */
.toolbar { display: flex; gap: .5rem; align-items: center; margin: .75rem 0; flex-wrap: wrap; }
.toolbar input[type="search"] { min-width: 220px; }
.toggle-inactive { display: flex; align-items: center; gap: .35rem; margin-left: auto;
  color: var(--muted); font-size: 13px; cursor: pointer; user-select: none; white-space: nowrap; }
.toggle-inactive input { cursor: pointer; }
a.clear-filter, a.btn-sm.clear-filter { color: var(--muted); }
/* Inside a card the toolbar is a padded band above the scrolling table; it must
   not shrink. The search input flex-grows so it fills the row at 1/3 width. */
.card > .toolbar { flex: 0 0 auto; margin: 0; padding: .5rem .75rem;
  border-bottom: 1px solid var(--border); }
.card > .toolbar input[type="search"] { flex: 1 1 140px; min-width: 0; }

/* --- Entity list panes: optional filter sidebar + list card + detail card ---
   Full-height split layout mirroring the legacy site: an optional ~220px filter
   sidebar, then list : detail split ~1/3 : 2/3. The row fills the content area
   and every pane is a full-height card that scrolls internally. */
.list-grid { flex: 1 1 auto; min-height: 0; display: flex; }
.entity-panes { flex: 1 1 auto; min-height: 0; display: flex; align-items: stretch; gap: 1rem; }
.entity-sidebar { flex: 0 0 220px; }
.entity-sidebar-head { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between;
  padding: .5rem .7rem; background: var(--surface-2); border-bottom: 1px solid var(--border);
  font-weight: 600; font-size: 12.5px; text-transform: uppercase; letter-spacing: .03em; color: var(--muted); }
.sidebar-toggle { background: transparent; color: var(--muted); border: 0; padding: .3rem .5rem;
  margin: -.3rem -.5rem; cursor: pointer; font-size: 14px; line-height: 1; border-radius: 4px; }
.sidebar-toggle:hover { color: var(--text); background: var(--border); }
/* BUG 2: rows must not compress. flex-shrink:0 keeps each link at a comfortable
   height; the list itself scrolls (flex-grow + overflow-y:auto). */
.entity-sidebar-list { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
  overflow-y: auto; }
.entity-sidebar-list a { flex: 0 0 auto; display: block; padding: .5rem .7rem; line-height: 1.3;
  border-bottom: 1px solid var(--border); color: var(--text); font-size: 13px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.entity-sidebar-list a:last-child { border-bottom: 0; }
.entity-sidebar-list a:hover { background: var(--surface-2); text-decoration: none; }
.entity-sidebar-list a.active { background: var(--accent); color: var(--accent-text); font-weight: 600; }
.entity-sidebar-list a.inactive { opacity: .55; }
.entity-main { flex: 1 1 0; min-width: 0; }

.with-sidebar.collapsed .entity-sidebar { flex-basis: 34px; }
.with-sidebar.collapsed .entity-sidebar-head span, .with-sidebar.collapsed .entity-sidebar-list { display: none; }
.with-sidebar.collapsed .sidebar-toggle { transform: rotate(180deg); }

/* List-pane collapse (only present when the editor pane is open): shrink the
   list card + filter sidebar to a narrow rail so the editor pane fills the
   width. The rail carries a single expand button. */
.list-expand { display: none; align-self: stretch; flex: 0 0 34px; background: var(--surface);
  color: var(--muted); border: 1px solid var(--border); border-radius: var(--radius);
  cursor: pointer; font-size: 14px; line-height: 1; padding: .5rem 0; font-weight: 600; }
.list-expand:hover { color: var(--text); border-color: var(--accent); }
.entity-panes.list-collapsed .entity-main,
.entity-panes.list-collapsed .entity-sidebar { display: none; }
.entity-panes.list-collapsed .list-expand { display: block; }
.entity-panes.list-collapsed .entity-editor-pane { flex: 1 1 auto; }

/* Detail/editor pane: the right ~2/3 card, always present on split pages. Holds
   either the inline editor form or a centered empty state. Scrolls internally. */
.entity-editor-pane, .entity-detail-pane { flex: 2 1 0; min-width: 0; padding: 1.25rem 2rem;
  overflow-y: auto; }
.entity-editor-pane .list-header { margin-bottom: .5rem; }
.entity-detail-pane { align-items: center; justify-content: center; }
.detail-empty { text-align: center; color: var(--muted); }
.detail-empty-icon { font-size: 2.5rem; line-height: 1; opacity: .5; margin-bottom: .6rem; }
.detail-empty p { margin: 0; font-size: 14px; }
tr.editing { background: color-mix(in srgb, var(--accent) 10%, transparent); }
tr.editing:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }

/* --- Multi-column edit forms: sections/fields flow into a responsive grid --- */
section.form form, .entity-editor-pane form { display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: .35rem 1.5rem; align-items: start; }
/* Standalone form pages live inside a full-height card (.form-card); the form
   itself is capped so it doesn't sprawl into too many thin columns. */
section.form.page-card { max-width: none; }
section.form.page-card form { max-width: 1100px; }
section.form form h2.form-section, .entity-editor-pane form h2.form-section,
section.form form .form-actions, .entity-editor-pane form .form-actions,
section.form form label.field:has(textarea), .entity-editor-pane form label.field:has(textarea) {
  /* :has() covers both the generic engine's `.field-{kind}` labels and the
     hand-written scheduling/relation forms, which only ever use plain `.field`.
     The item picker is a single <select> now (no vault-filter companion), so it
     packs into the grid like any other field instead of forcing a full row. */
  grid-column: 1 / -1;
}
/* Section headers open a fresh row so each group's fields start at column 1. */
section.form form h2.form-section, .entity-editor-pane form h2.form-section { margin-top: .6rem; }
/* Grid gap owns the vertical rhythm here; zero the field margins so rows in a
   column don't double up on spacing. */
section.form form label.field, .entity-editor-pane form label.field { margin: 0; }
/* When the list pane is collapsed the editor spans the page — let it pack more
   columns so a typical form fits without vertical scrolling. */
.entity-panes.list-collapsed .entity-editor-pane form {
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
/* --- Responsive: below 900px the fixed-height desktop shell relaxes into
   normal document flow so the page can scroll on mobile. Cards become
   auto-height blocks and the split layout stacks. --- */
@media (max-width: 900px) {
  body { overflow: auto; }
  .content { overflow: visible; display: block; padding: .75rem; }
  .list-grid { display: block; }
  .entity-panes { flex-direction: column; align-items: stretch; }
  /* Stacked panes have no vertical divider to drag. (The flex reset below is
     !important, so it also overrides any inline grow the resizer stored.) */
  .pane-resizer { display: none; }
  /* !important: page-specific flex-ratio tuning (e.g. .history-panes .entity-main,
     .scheduling-panes .entity-main) outranks this by specificity, which left those
     pages' panes at flex-basis 0 with no definite ancestor height to grow into --
     the classic flex collapse. This reset must always win on mobile. */
  .entity-sidebar, .entity-main, .entity-editor-pane, .entity-detail-pane {
    flex: 0 0 auto !important; width: 100%; }
  .card, .page-card { min-height: 0; }
  .card-table-scroll, .page-card .card-body { overflow: visible; }
  .entity-sidebar-list { max-height: 40vh; }
  .entity-editor-pane form { grid-template-columns: 1fr; }
  /* The detail empty-state has no meaning stacked below the list — hide it. */
  .entity-detail-pane { display: none; }
}
@media (max-width: 640px) {
  section.form form { display: block; }
  /* grid gap is gone in block layout — restore per-field spacing */
  section.form form label.field { margin: .5rem 0; }
}

/* --- Batch editor: run-time picker ------------------------------------------
   Left: hour : minute : AM/PM combobox row, then the zone toggle. Right: a
   Repeat daily toggle over a Monday-Sunday column. Labels wrapping inputs have
   to out-specify the generic "form label input { display:block; min-width:280px }"
   rule, hence the "form ..." prefixes. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
fieldset.run-time-picker { border: 0; padding: 0; margin: 0; min-width: 0; grid-column: 1 / -1; }
.rt-cols { display: grid; grid-template-columns: minmax(0, 1fr) 11rem; gap: 1.5rem; align-items: start; }
.rt-left .hint { display: block; margin-top: .3rem; }
.rt-tz-label { margin-top: 1rem; }
.rt-time { display: inline-flex; align-items: center; gap: .3rem; }
.rt-sep { color: var(--muted); font-size: 16px; }
.rt-cb { position: relative; display: inline-block; }
form .run-time-picker .rt-cb input { display: inline-block; width: 3rem; min-width: 0; margin: 0;
  text-align: center; font-variant-numeric: tabular-nums; }
.rt-list { position: absolute; left: -.25rem; width: 3.5rem; margin: 0; padding: 0; list-style: none;
  overflow-y: auto; scrollbar-width: thin; z-index: 20; background: var(--surface);
  border: 1px solid var(--border); border-radius: 6px;
  box-shadow: 0 6px 18px color-mix(in srgb, #000 18%, transparent); }
.rt-list li { height: 28px; line-height: 28px; text-align: center; cursor: pointer; font-variant-numeric: tabular-nums; }
.rt-list li:hover { background: var(--surface-2); }
.rt-list li.is-active { background: var(--accent); color: var(--accent-text); }
.run-time-picker .seg { display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
form .run-time-picker .seg label { display: inline-flex; margin: 0; position: relative; cursor: pointer; }
form .run-time-picker .seg label + label { border-left: 1px solid var(--border); }
form .run-time-picker .seg label input, form .run-time-picker .rt-day input {
  position: absolute; opacity: 0; width: 1px; height: 1px; min-width: 0; margin: 0; }
.run-time-picker .seg span { padding: .3rem .9rem; font-size: 13px; background: var(--surface); user-select: none; }
.run-time-picker .seg input:checked + span { background: var(--accent); color: var(--accent-text); }
.run-time-picker button.rt-daily { width: 100%; margin: 0 0 .45rem; background: var(--surface);
  color: var(--text); border: 1px solid var(--border); font-weight: 500; }
.run-time-picker button.rt-daily[aria-pressed="true"] { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.rt-days { display: flex; flex-direction: column; gap: .3rem; }
form .run-time-picker label.rt-day { display: flex; align-items: center; gap: .55rem; margin: 0; position: relative;
  cursor: pointer; border: 1px solid var(--border); border-radius: 6px; padding: .3rem .6rem; font-size: 13px; user-select: none; }
.rt-day span::before { content: ""; display: inline-block; width: 14px; height: 14px; margin-right: .5rem;
  vertical-align: -2px; border: 1px solid var(--border); border-radius: 3px; background: var(--surface); }
form .run-time-picker label.rt-day:has(input:checked) { background: color-mix(in srgb, var(--accent) 14%, transparent); border-color: var(--accent); }
.rt-day input:checked + span::before { background: var(--accent); border-color: var(--accent);
  content: "\2713"; color: var(--accent-text); font-size: 11px; line-height: 14px; text-align: center; }
.run-time-picker input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.rt-summary { margin: .8rem 0 .2rem; padding-top: .6rem; border-top: 1px solid var(--border); }
.rt-summary:empty { display: none; }
@media (max-width: 700px) { .rt-cols { grid-template-columns: 1fr; } }
