/* ==========================================================================
   iTrend Agent — application theme (Bootstrap 5.3 + HTMX)
   Dark is the default; light is applied via [data-bs-theme="light"].
   ========================================================================== */

/* ---------- tokens ---------- */
:root,
[data-bs-theme="dark"] {
  --bs-body-bg: #0a0b0e;
  --bs-body-color: #e7e9ec;
  --bs-border-color: rgba(255, 255, 255, .085);
  --bs-emphasis-color: #fff;
  --bs-secondary-color: rgba(231, 233, 236, .55);

  --app-bg: #0a0b0e;
  /* Chrome sits one step above the canvas rather than level with it. At
     rgba(14,16,20,.92) over #0a0b0e the topbar and sidebar composited to within
     three points of the page behind them, so the only thing separating the
     shell from the content was a hairline border. */
  --app-panel: #101217;
  --app-card: #14161a;
  --app-card-2: rgba(255, 255, 255, .035);
  --app-line: rgba(255, 255, 255, .085);
  --app-line-soft: rgba(255, 255, 255, .05);
  --app-fg: #e7e9ec;
  --app-muted: rgba(231, 233, 236, .5);
  --app-faint: rgba(231, 233, 236, .32);
  --app-hover: rgba(255, 255, 255, .06);
  --app-grid-dot: rgba(255, 255, 255, .075);
  --app-inset: rgba(0, 0, 0, .3);

  --ok: oklch(.8 .14 155);
  --ok-bg: oklch(.72 .16 155 / .15);
  --warn: oklch(.85 .14 75);
  --warn-bg: oklch(.78 .16 75 / .15);
  --err: oklch(.8 .15 25);
  --err-bg: oklch(.66 .19 25 / .17);
  --info: oklch(.86 .11 285);
  --info-bg: oklch(.68 .16 285 / .2);
  --dot-ok: oklch(.72 .16 155);
  --dot-warn: oklch(.78 .16 75);
  --dot-err: oklch(.66 .19 25);
  --dot-info: oklch(.78 .13 285);

  --accent: oklch(.68 .16 285);
  --accent-2: oklch(.62 .15 250);
  --accent-fg: #fff;
  --accent-soft: oklch(.68 .16 285 / .18);
  --accent-line: oklch(.68 .16 285 / .35);

  /* Keyboard focus. The browser default resolves to near-black here, which on a
     #0a0b0e page is an invisible focus ring -- the app was unusable by keyboard
     without guessing. Every interactive surface below draws this instead. */
  --focus-ring: oklch(.68 .16 285 / .55);
  --focus-halo: oklch(.68 .16 285 / .22);
  --select-bg: oklch(.68 .16 285 / .32);
}

[data-bs-theme="light"] {
  --bs-body-bg: #f4f5f7;
  --bs-body-color: #1a1c21;
  --bs-border-color: rgba(22, 24, 30, .12);
  --bs-emphasis-color: #0b0c0f;
  --bs-secondary-color: rgba(26, 28, 33, .6);

  --app-bg: #f4f5f7;
  --app-panel: #ffffff;
  --app-card: #ffffff;
  --app-card-2: rgba(22, 24, 30, .025);
  --app-line: rgba(22, 24, 30, .12);
  --app-line-soft: rgba(22, 24, 30, .07);
  --app-fg: #1a1c21;
  --app-muted: rgba(26, 28, 33, .58);
  --app-faint: rgba(26, 28, 33, .42);
  --app-hover: rgba(22, 24, 30, .05);
  --app-grid-dot: rgba(22, 24, 30, .13);
  --app-inset: rgba(22, 24, 30, .03);

  --ok: oklch(.5 .14 155);
  --ok-bg: oklch(.6 .14 155 / .14);
  --warn: oklch(.52 .13 75);
  --warn-bg: oklch(.7 .14 75 / .18);
  --err: oklch(.53 .18 25);
  --err-bg: oklch(.66 .19 25 / .12);
  --info: oklch(.5 .16 285);
  --info-bg: oklch(.62 .16 285 / .13);
  --dot-ok: oklch(.58 .15 155);
  --dot-warn: oklch(.66 .15 75);
  --dot-err: oklch(.56 .19 25);
  --dot-info: oklch(.55 .16 285);

  --accent: oklch(.55 .17 285);
  --accent-2: oklch(.52 .15 250);
  --accent-fg: #fff;
  --accent-soft: oklch(.55 .17 285 / .12);
  --accent-line: oklch(.55 .17 285 / .3);

  --focus-ring: oklch(.55 .17 285 / .7);
  --focus-halo: oklch(.55 .17 285 / .18);
  --select-bg: oklch(.55 .17 285 / .2);
}

/* ---------- base ---------- */
* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  background: var(--app-bg);
  color: var(--app-fg);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  font-size: 13px;
  overflow: hidden;
}

body.page-auth { overflow: auto; }

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-2); }

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--app-line); border-radius: 8px; }
::-webkit-scrollbar-thumb:hover { background: var(--app-muted); }
::-webkit-scrollbar-track { background: transparent; }

@keyframes pulseDot { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.82); } }
@keyframes dashFlow { to { stroke-dashoffset: -24; } }
@keyframes fadeUp { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

::selection { background: var(--select-bg); color: var(--bs-emphasis-color); }

/* ---------- keyboard focus ----------
   One ring for the whole product. Every control below replaces a Bootstrap or
   browser default with a custom class, and each one inherited the UA outline --
   which resolves to near-black and therefore vanishes against the dark canvas.

   `:focus-visible` rather than `:focus`, so a pointer click does not leave a
   ring behind on a button that was only pressed. The halo is drawn as a second
   box-shadow so it layers over a control that already has one (the accent
   button's gradient, the card's elevation) instead of replacing it. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: inherit;
}

/* Controls whose own border is the shape being focused read better with the
   ring tucked against them rather than floating two pixels off. */
.icon-btn:focus-visible,
.btn-row-action:focus-visible,
.search-pill:focus-visible,
.sb-link:focus-visible,
.log-tab:focus-visible,
.seg button:focus-visible,
.seg a:focus-visible,
.dropdown-item:focus-visible,
.toolbar-field:focus-within {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-halo);
}

/* The switch has no focusable box of its own -- the input inside it is hidden --
   so the track is what has to show the ring. */
.mini-switch input:focus-visible + .track,
.form-check-input:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* A table row that opens something is reachable by keyboard on the pages that
   give it a tabindex; without this the only feedback was the hover background,
   which a keyboard never triggers. */
.app-table tbody tr:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: -2px;
}

/* Skip link. Present on every page ahead of the topbar, off-screen until it is
   focused -- seven sidebar links and a topbar sit between the top of the
   document and the content on every route otherwise. */
.skip-link {
  position: absolute; left: 12px; top: -60px; z-index: 200;
  padding: 8px 14px; border-radius: 10px;
  background: var(--accent); color: var(--accent-fg);
  font-size: 12.5px; font-weight: 600; text-decoration: none;
  transition: top .12s ease;
}
.skip-link:focus { top: 12px; color: var(--accent-fg); }

/* The skip link's target is focusable so the jump actually moves the caret, but
   it is a region, not a control -- a ring around the entire content area is not
   what "you are here" should look like. */
.app-main:focus, .app-main:focus-visible { outline: none; }

/* ---------- numerals ----------
   Proportional digits make a column of figures ragged and make a counter that
   ticks (a run timer, a live total) jitter as its digits change width. Every
   place this app prints a number is a place that wants tabular. */
.stat-value,
.metric-cell .m-value,
.chart-scale span,
.chart-axis span,
.app-table td,
.log-line .ts,
.mono,
.sb-link .sb-tag,
.chip { font-variant-numeric: tabular-nums; }

/* ---------- app shell ---------- */
.app-shell { display: flex; flex-direction: column; height: 100vh; min-width: 0; }
.app-body { flex: 1; display: flex; min-height: 0; }
.app-main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.app-scroll { flex: 1; overflow: auto; padding: 20px 22px 32px; }

.app-topbar {
  flex: none; height: 54px; display: flex; align-items: center; gap: 12px;
  padding: 0 12px 0 14px; background: var(--app-panel);
  border-bottom: 1px solid var(--app-line); z-index: 40; white-space: nowrap;
}
.brand-mark {
  width: 26px; height: 26px; border-radius: 8px; flex: none; display: flex;
  align-items: center; justify-content: center; color: #fff; font-size: 12px;
  background: linear-gradient(145deg, var(--accent), var(--accent-2));
  box-shadow: 0 2px 10px oklch(.68 .16 285 / .4);
}
.brand-name { font-size: 13px; font-weight: 600; letter-spacing: -.01em; color: var(--app-fg); }
.brand-logo { height: 28px; width: auto; display: block; flex: none; }
.brand-logo.lg { height: 38px; }

.crumb { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--app-faint); }
.crumb .sep { font-size: 9px; }
.crumb .cur { color: var(--app-fg); font-weight: 500; }

.icon-btn {
  height: 30px; min-width: 30px; padding: 0 8px; border-radius: 9px;
  border: 1px solid var(--app-line); background: var(--app-card-2);
  color: var(--app-muted); display: inline-flex; align-items: center;
  justify-content: center; gap: 6px; font-size: 12px; cursor: pointer;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.icon-btn:hover { background: var(--app-hover); color: var(--app-fg); }
.icon-btn.sm { height: 26px; min-width: 26px; border-radius: 7px; font-size: 11px; }
.icon-btn.danger:hover { background: var(--err-bg); color: var(--err); border-color: var(--err); }

.btn-accent {
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
  border: 1px solid var(--accent-line); color: var(--accent-fg);
  font-weight: 600; font-size: 12px; border-radius: 9px;
}
.btn-accent:hover { color: #fff; filter: brightness(1.08); }
.btn-run {
  background: var(--ok-bg); border: 1px solid var(--dot-ok); color: var(--ok);
  font-weight: 600; font-size: 12px; border-radius: 9px;
}
.btn-run:hover { background: oklch(.72 .16 155 / .26); color: var(--ok); }

/* A list row's primary action — the button that opens the thing the row is
   about. Sized above .icon-btn.sm because it is the one control in the row and
   was reading as an afterthought next to the agent name.

   `white-space: nowrap` is load-bearing, not cosmetic: the label is two words
   and the flex parent shrinks when a row carries long secondary text, which
   broke "Workflow Config" across two lines on some rows and not others. The
   button now keeps its width and the text beside it wraps instead. */
.btn-row-action {
  height: 32px; padding: 0 12px; border-radius: 9px;
  border: 1px solid var(--app-line); background: var(--app-card-2);
  color: var(--app-fg); font-size: 12px; font-weight: 500;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  white-space: nowrap; cursor: pointer; text-decoration: none;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.btn-row-action:hover {
  background: var(--app-hover); color: var(--accent); border-color: var(--accent-line);
}
/* The chevron reads as "this navigates somewhere" and nudges on hover, which
   distinguishes it from the buttons that act in place. */
.btn-row-action .chevron { font-size: 10px; opacity: .55; transition: transform .15s ease, opacity .15s ease; }
.btn-row-action:hover .chevron { transform: translateX(2px); opacity: 1; }

.search-pill {
  display: flex; align-items: center; gap: 7px; height: 30px; padding: 0 10px;
  border-radius: 9px; background: var(--app-card-2); border: 1px solid var(--app-line);
  color: var(--app-faint); font-size: 12px; min-width: 190px; cursor: pointer;
}
.kbd {
  font: 600 10px ui-monospace, Menlo, monospace; padding: 1px 5px;
  border-radius: 4px; background: var(--app-hover); color: var(--app-muted);
}
.avatar {
  width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center;
  justify-content: center; font-size: 11px; font-weight: 600; color: #fff;
  background: linear-gradient(145deg, var(--accent-2), var(--accent));
}

.switch-group {
  display: flex; align-items: center; gap: 14px; height: 30px; padding: 0 12px;
  border-radius: 9px; background: var(--app-card-2); border: 1px solid var(--app-line);
}
.mini-switch { display: flex; align-items: center; gap: 7px; font-size: 11px; color: var(--app-muted); cursor: pointer; margin: 0; }
.mini-switch input { display: none; }
.mini-switch .track {
  position: relative; width: 28px; height: 16px; border-radius: 20px;
  background: var(--app-line); flex: none; transition: background .16s ease;
}
.mini-switch .track::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 12px; height: 12px;
  border-radius: 50%; background: var(--app-muted); transition: left .16s ease, background .16s ease;
}
.mini-switch input:checked + .track { background: var(--accent); }
.mini-switch input:checked + .track::after { left: 14px; background: #fff; }

/* ---------- sidebar ---------- */
.app-sidebar {
  flex: none; width: 216px; background: var(--app-panel); border-right: 1px solid var(--app-line);
  display: flex; flex-direction: column; padding: 12px 10px; transition: width .16s ease;
}
.app-sidebar.collapsed { width: 56px; padding: 12px 0; align-items: center; }
.sb-title {
  font: 600 10px/1 ui-monospace, Menlo, monospace; letter-spacing: .12em;
  color: var(--app-faint); padding: 2px 6px 10px; display: flex;
  align-items: center; justify-content: space-between;
}
.app-sidebar.collapsed .sb-title { padding: 2px 0 10px; justify-content: center; }
.app-sidebar.collapsed .sb-title span { display: none; }
.sb-nav { display: flex; flex-direction: column; gap: 2px; }
.app-sidebar.collapsed .sb-nav { align-items: center; }
.sb-link {
  display: flex; align-items: center; gap: 10px; height: 32px; padding: 0 9px;
  border-radius: 9px; color: var(--app-muted); font-size: 12.5px; cursor: pointer;
  border: 1px solid transparent; transition: background .14s ease, color .14s ease;
  background: transparent; width: 100%; text-align: left;
  font-family: inherit; text-decoration: none;
}
.sb-link:hover { background: var(--app-hover); color: var(--app-fg); }
.sb-link i { font-size: 13px; width: 16px; flex: none; text-align: center; }
.sb-link .sb-label { flex: 1; min-width: 0; }
.sb-link .sb-tag { font: 600 10px ui-monospace, monospace; color: var(--app-faint); }
.sb-link.active {
  background: linear-gradient(90deg, var(--accent-soft), transparent);
  border-color: var(--accent-line); color: var(--app-fg); font-weight: 500;
}
.sb-link.active i { color: var(--accent); }
.app-sidebar.collapsed .sb-link { width: 36px; justify-content: center; padding: 0; }
.app-sidebar.collapsed .sb-label,
.app-sidebar.collapsed .sb-tag,
.app-sidebar.collapsed .sb-version { display: none; }
.sb-version {
  border-radius: 12px; border: 1px solid var(--app-line); background: var(--app-card-2);
  padding: 10px 12px; margin-top: auto;
  display: flex; align-items: center; gap: 8px;
}

/* ---------- generic surfaces ---------- */
.panel {
  border-radius: 14px; border: 1px solid var(--app-line); background: var(--app-card);
  overflow: hidden;
}
[data-bs-theme="dark"] .panel { box-shadow: 0 8px 24px rgba(0, 0, 0, .28); }
[data-bs-theme="light"] .panel { box-shadow: 0 1px 2px rgba(16, 18, 24, .05); }
.panel-head {
  display: flex; align-items: center; gap: 10px; padding: 12px 14px;
  border-bottom: 1px solid var(--app-line-soft);
}
.panel-title { font-size: 13px; font-weight: 600; letter-spacing: -.01em; margin: 0; }
.panel-body { padding: 14px; }

.section-label {
  font: 600 10px ui-monospace, Menlo, monospace; letter-spacing: .12em;
  color: var(--app-faint); text-transform: uppercase;
}
.page-head { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 18px; flex-wrap: wrap; }
.page-title { font-size: 19px; font-weight: 650; letter-spacing: -.02em; margin: 0; }
.page-sub { font-size: 12.5px; color: var(--app-muted); margin: 4px 0 0; }

.min-w-0 { min-width: 0; }
.min-h-0 { min-height: 0; }
.muted { color: var(--app-muted); }
.faint { color: var(--app-faint); }
.mono { font-family: ui-monospace, Menlo, Monaco, monospace; }
.fs-11 { font-size: 11px; }
.fs-12 { font-size: 12px; }
.fs-13 { font-size: 13px; }

/* ---------- stats ---------- */
.stat-card {
  border-radius: 14px; border: 1px solid var(--app-line); background: var(--app-card);
  padding: 14px 15px; height: 100%; display: flex; flex-direction: column; gap: 8px;
}
.stat-card .stat-top { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--app-muted); }

/* The caption above the figure. Two spellings are in the templates -- the
   dashboard writes .stat-label inside its own flex row, the other four pages
   write .stat-top around the icon and the words together -- and .stat-label was
   never given a rule, so the dashboard printed its captions at body size in
   full-strength foreground while every other page printed them small and muted.
   Same caption, same weight, wherever it is written. */
.stat-card .stat-label { font-size: 11.5px; color: var(--app-muted); }

/* Hover lift. These cards are the first thing on five of the pages; a border
   that answers the pointer is what separates a live surface from a printed one. */
.stat-card { transition: border-color .15s ease, transform .15s ease; }
.stat-card:hover { border-color: var(--app-muted); }
/* One line, always. A wrapped headline ("2 hours,\n29 minutes") pushed its
   caption down and knocked the card out of line with the three beside it, so
   the value ellipsises rather than wrapping and the long forms are shortened at
   the source -- see the `since_short` filter. */
.stat-value {
  font-size: 23px; font-weight: 650; letter-spacing: -.03em; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  display: flex; align-items: baseline; gap: 5px;
}
/* The denominator of a ratio and the unit after a number: same line, quieter,
   and never the thing that decides the card's height. */
.stat-value .stat-unit { font-size: 12px; font-weight: 500; color: var(--app-muted); letter-spacing: 0; }
/* Pin the foot to the bottom so cards in a row line up even when one caption
   runs to two lines, and let a long one ellipsise instead of spilling past the
   card edge. */
.stat-foot {
  font-size: 11px; color: var(--app-faint); display: flex; align-items: center; gap: 6px;
  margin-top: auto; min-width: 0; overflow: hidden;
}
.stat-foot > .text-truncate { min-width: 0; }
.trend-up { color: var(--ok); }
.trend-down { color: var(--err); }

.chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px;
  border-radius: 20px; font-size: 10.5px; font-weight: 600; letter-spacing: .02em;
  background: var(--app-hover); color: var(--app-muted); border: 1px solid transparent;
}
.chip-ok { background: var(--ok-bg); color: var(--ok); }
.chip-warn { background: var(--warn-bg); color: var(--warn); }
.chip-err { background: var(--err-bg); color: var(--err); }
.chip-info { background: var(--info-bg); color: var(--info); }
.chip-idle { background: var(--app-hover); color: var(--app-muted); }

.dot { width: 7px; height: 7px; border-radius: 50%; flex: none; display: inline-block; }
.dot-ok { background: var(--dot-ok); box-shadow: 0 0 8px var(--dot-ok); }
.dot-warn { background: var(--dot-warn); box-shadow: 0 0 8px var(--dot-warn); }
.dot-err { background: var(--dot-err); box-shadow: 0 0 8px var(--dot-err); }
.dot-info { background: var(--dot-info); box-shadow: 0 0 10px var(--dot-info); }
.dot-idle { background: var(--app-line); }

.icon-tile {
  width: 30px; height: 30px; flex: none; border-radius: 9px; display: flex;
  align-items: center; justify-content: center; font-size: 13px;
  background: var(--accent-soft); color: var(--accent);
}
.icon-tile.ok { background: var(--ok-bg); color: var(--ok); }
.icon-tile.warn { background: var(--warn-bg); color: var(--warn); }
.icon-tile.err { background: var(--err-bg); color: var(--err); }
.icon-tile.info { background: var(--info-bg); color: var(--info); }
.icon-tile.lg { width: 38px; height: 38px; border-radius: 11px; font-size: 16px; }

/* ---------- tables ---------- */
.app-table { width: 100%; margin: 0; font-size: 12.5px; border-collapse: separate; border-spacing: 0; }
.app-table thead th {
  position: sticky; top: 0; z-index: 2; background: var(--app-card);
  font: 600 10px ui-monospace, Menlo, monospace; letter-spacing: .1em; text-transform: uppercase;
  color: var(--app-faint); padding: 9px 14px; border-bottom: 1px solid var(--app-line);
  white-space: nowrap; text-align: left;
}
/* A sticky header sitting on top of rows it is overlapping should say so. The
   border alone reads the same whether the table is scrolled or not, so rows
   appear to end at the header rather than pass under it. */
[data-bs-theme="dark"] .app-table thead th { box-shadow: 0 4px 12px -6px rgba(0, 0, 0, .8); }
[data-bs-theme="light"] .app-table thead th { box-shadow: 0 4px 10px -7px rgba(16, 18, 24, .45); }

/* Figures right-align against each other; text stays left. Applied per cell
   rather than per column because these tables are built row by row. */
.app-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.app-table tbody td {
  padding: 10px 14px; border-bottom: 1px solid var(--app-line-soft);
  color: var(--app-fg); vertical-align: middle;
}
.app-table tbody tr:hover td { background: var(--app-hover); }
/* A row that opens something. The hover background above already signals the
   row is live; this only makes the pointer agree with it. */
.app-table tbody tr[hx-get] { cursor: pointer; }
.app-table tbody tr:last-child td { border-bottom: none; }
.table-wrap { max-height: 100%; overflow: auto; }

/* Compact rows: long campaign names and keywords wrap to four or five lines
   otherwise, which makes a handful of rows fill the screen. One line each,
   ellipsised, with the full value in the title attribute. */
.app-table.compact tbody td { padding: 6px 12px; white-space: nowrap; }
.app-table.compact thead th { padding: 7px 12px; }
.truncate-cell { overflow: hidden; text-overflow: ellipsis; }

/* One line, ellipsis at the END, reading from the start of the name. Campaign
   names do share long prefixes, so the tail is often what distinguishes them --
   the full value is in the title attribute on every element that uses this, and
   the copy button next to it yields the whole string. */
.truncate-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.form-control, .form-select {
  background: var(--app-inset); border: 1px solid var(--app-line); color: var(--app-fg);
  font-size: 12.5px; border-radius: 9px;
}
.form-control:focus, .form-select:focus {
  background: var(--app-inset); color: var(--app-fg); border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.form-control::placeholder { color: var(--app-faint); }
.form-label { font-size: 11.5px; color: var(--app-muted); margin-bottom: 5px; }
.form-check-input:checked { background-color: var(--accent); border-color: var(--accent); }

.seg { display: inline-flex; padding: 3px; gap: 2px; border-radius: 10px; background: var(--app-card-2); border: 1px solid var(--app-line); }
.seg button, .seg a {
  border: none; background: transparent; color: var(--app-muted); font-size: 11.5px;
  padding: 4px 11px; border-radius: 7px; cursor: pointer; text-decoration: none;
  display: inline-flex; align-items: center;
}
.seg button:hover, .seg a:hover { color: var(--app-fg); }
.seg button.active, .seg a.active { background: var(--app-hover); color: var(--app-fg); font-weight: 600; }

/* ---------- workflow toolbar (agent workflow editor) ----------
   wf-shell stacks the toolbar above a canvas + inspector row, so the toolbar
   spans the full width and its Run button lands at the page's right edge. */
.wf-shell { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.wf-body { flex: 1; min-width: 0; min-height: 0; display: flex; }
.wf-toolbar {
  flex: none; display: flex; align-items: center; gap: 10px;
  padding: 10px 16px; min-height: 58px;
  background: var(--app-panel); border-bottom: 1px solid var(--app-line);
}
/* The action group never wraps or shrinks — Save and Run stay reachable, and
   the run status beside them ellipsises rather than pushing them off-screen. */
.wf-toolbar-actions { display: flex; align-items: center; gap: 8px; flex: none; }
.wf-toolbar-actions #wf-run-meta {
  max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* A form control that sits on the same visual line as the toolbar's buttons. */
.toolbar-field {
  display: inline-flex; align-items: center; gap: 6px; height: 38px; padding: 0 10px;
  border-radius: 10px; border: 1px solid var(--app-line); background: var(--app-card-2);
  color: var(--app-muted); font-size: 12px; cursor: pointer;
}
.toolbar-field:hover { border-color: var(--app-muted); }
.toolbar-field:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.toolbar-field select {
  border: 0; background: transparent; color: var(--app-fg); font-size: 12px; font-weight: 500;
  padding: 0 2px; outline: none; cursor: pointer; max-width: 150px;
}
.toolbar-field select:disabled { color: var(--app-muted); cursor: not-allowed; }

/* ---------- workflow canvas ---------- */
.canvas-wrap {
  flex: 1; position: relative; min-height: 0; overflow: hidden;
  background-color: var(--app-bg);
  background-image: radial-gradient(var(--app-grid-dot) 1.1px, transparent 1.1px);
  background-size: 22px 22px; background-position: -11px -11px;
}
.canvas-scroll { position: absolute; inset: 0; overflow: auto; padding: 26px 30px; }
.canvas-stage { position: relative; width: 790px; height: 770px; margin: 0 auto; }

/* Zoom wrapper: the stage is scaled with a transform (which doesn't affect
   layout), so this sizer carries the scaled dimensions to keep scrolling right. */
.canvas-sizer { position: relative; margin: 0 auto; }
.canvas-sizer .canvas-stage { margin: 0; }

/* Compact screen: hand the canvas the whole window, keeping the workflow
   toolbar so Run / Save / exit stay reachable. */
body.wf-compact .app-topbar,
body.wf-compact .app-sidebar,
body.wf-compact .log-dock { display: none; }

.wf-node {
  position: absolute; width: 240px; height: 96px; border-radius: 14px;
  background: var(--app-card); border: 1px solid var(--app-line);
  padding: 11px 13px; display: flex; flex-direction: column; justify-content: space-between;
  cursor: pointer;
  transition: border-color .3s ease, background .3s ease, box-shadow .3s ease;
}
[data-bs-theme="dark"] .wf-node { box-shadow: 0 10px 26px rgba(0, 0, 0, .45), inset 0 1px 0 rgba(255, 255, 255, .04); }
[data-bs-theme="light"] .wf-node { box-shadow: 0 2px 8px rgba(16, 18, 24, .07); }
.wf-node:hover { border-color: var(--app-muted); }
.wf-node.is-dashed { border-style: dashed; }

/* Selection (whose config the inspector shows) is an outline only, so it can
   never be mistaken for the run highlight below. */
.wf-node.is-selected { border-color: var(--accent); }

/* The node the graph is inside right now. This is the shade that travels: the
   class moves from node to node as the run progresses and the colour eases in,
   while the ring behind it glides across at the same time. */
.wf-node.is-active {
  border-color: var(--accent);
  background: linear-gradient(180deg, var(--accent-soft), var(--app-card));
}
.wf-node.is-active .n-sub { color: var(--accent); }
.wf-node .n-title { font-size: 12.5px; font-weight: 600; letter-spacing: -.01em; }
.wf-node .n-sub { font-size: 10.5px; color: var(--app-faint); }
/* Fixed width, so a node's status line doesn't reflow when its timing lands. */
.wf-node .n-time { font: 500 10px ui-monospace, Menlo, monospace; color: var(--app-faint); min-width: 36px; }

/* The node body is the only part a run ever rewrites; it fills the node box, so
   swapping it can't change the box's size or position. */
.wf-node-body { flex: 1; display: flex; flex-direction: column; justify-content: space-between; }

/* Flow connections. Their colour tracks progress -- edges between two finished
   nodes turn green, the edge feeding the active node lights up -- but they are
   restyled by toggling a class, never by re-rendering the SVG, so the canvas
   holds still. The colour eases in rather than snapping. */
.wf-link {
  stroke: var(--app-muted); opacity: .75; stroke-width: 2;
  transition: stroke .3s ease, opacity .3s ease;
}
.wf-link.is-done { stroke: var(--dot-ok); opacity: 1; }
.wf-link.is-active { stroke: var(--dot-info); opacity: 1; stroke-width: 2.4; }

@media (prefers-reduced-motion: reduce) {
  .wf-link { transition: none; }
}

/* The one moving element on the canvas: a highlight that glides onto whichever
   node the graph is currently inside. It sits behind the node boxes, so it reads
   as an outline around the active step and never covers its content. */
.wf-focus {
  position: absolute; border-radius: 19px; pointer-events: none;
  border: 2px solid var(--accent); background: var(--accent-soft);
  box-shadow: 0 0 0 5px var(--accent-soft);
  transition: left .4s cubic-bezier(.4, 0, .2, 1), top .4s cubic-bezier(.4, 0, .2, 1),
              width .4s cubic-bezier(.4, 0, .2, 1), height .4s cubic-bezier(.4, 0, .2, 1);
}
.wf-focus[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .wf-focus { transition: none; }
}
.port {
  position: absolute; width: 8px; height: 8px; border-radius: 50%;
  background: var(--app-card); border: 1.5px solid var(--app-muted);
}
.port.t { top: -4px; left: 50%; margin-left: -4px; }
.port.b { bottom: -4px; left: 50%; margin-left: -4px; }
.port.l { top: 50%; left: -4px; margin-top: -4px; }
.port.r { top: 50%; right: -4px; margin-top: -4px; }
.wf-node.is-selected .port { border-color: var(--accent); }

/* Solid, not blurred: a backdrop filter re-composites the whole area underneath
   every time the panel's contents change, which reads as flicker during a run. */
.canvas-float {
  position: absolute; z-index: 12; border-radius: 12px; border: 1px solid var(--app-line);
  background: var(--app-card);
}
[data-bs-theme="dark"] .canvas-float { box-shadow: 0 8px 22px rgba(0, 0, 0, .4); }

.minimap-node { position: absolute; width: 34px; height: 10px; border-radius: 3px; background: var(--app-muted); opacity: .5; }

/* ---------- inspector ---------- */
.inspector {
  flex: none; width: 326px; background: var(--app-panel); border-left: 1px solid var(--app-line);
  display: flex; flex-direction: column; min-height: 0;
}
/* The whole panel is one form with one Save: the fields scroll, the button does
   not, so it stays in reach from the schedule at the bottom as well as the
   inputs at the top. */
.inspector-form { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.inspector-body { flex: 1; overflow: auto; padding: 14px; display: flex; flex-direction: column; gap: 14px; }
.inspector-foot {
  flex: none; padding: 10px 14px 12px; background: var(--app-panel);
  border-top: 1px solid var(--app-line);
}
.field-box { border-radius: 12px; border: 1px solid var(--app-line); background: var(--app-card-2); padding: 11px 12px; }

/* Amazon write-back: both wordings are in the markup and the checkbox picks one,
   so ticking the box reads as live straight away -- before, and without, a save. */
.apply-switch .apply-live { display: none; }
.apply-switch input:checked ~ * .apply-dry { display: none; }
.apply-switch input:checked ~ * .apply-live { display: inline; }
.apply-switch:has(input:checked) { border-color: var(--dot-warn); }
.readonly-input {
  height: 30px; border-radius: 8px; border: 1px solid var(--app-line); background: var(--app-inset);
  display: flex; align-items: center; padding: 0 9px;
  font: 500 11.5px ui-monospace, Menlo, monospace; color: var(--app-fg);
}
.code-box {
  border-radius: 12px; border: 1px solid var(--app-line); background: var(--app-inset);
  padding: 10px 12px; font: 400 10.5px/1.75 ui-monospace, Menlo, monospace;
  color: var(--app-muted); white-space: pre-wrap; overflow-x: auto;
}
.var-token { color: var(--accent); }
.range-thin { height: 4px; }
.form-range::-webkit-slider-thumb { background: var(--accent); }
.form-range::-moz-range-thumb { background: var(--accent); }

/* ---------- log console ---------- */
.log-dock { flex: none; background: var(--app-panel); border-top: 1px solid var(--app-line); display: flex; flex-direction: column; height: 200px; }
.log-dock.collapsed { height: 34px; }
/* Full-screen output: the canvas steps aside and the dock takes the whole main
   area. A long execution log or a wide history table is unreadable in 200px,
   and this is cheaper than a second page for the same data. */
.log-dock.fullscreen { flex: 1 1 auto; height: auto; min-height: 0; border-top: none; }
body.dock-full .canvas-wrap { display: none; }

/* The tab panels are switched with the `hidden` attribute, but one of them
   carries Bootstrap's .d-flex, whose `display: flex !important` outranks the
   browser's `[hidden] { display: none }`. Without this the history table shows
   underneath whichever tab is actually selected. */
.log-dock [data-log-panel][hidden] { display: none !important; }
.log-tabs { flex: none; height: 38px; display: flex; align-items: center; gap: 4px; padding: 0 12px; border-bottom: 1px solid var(--app-line-soft); white-space: nowrap; overflow-x: auto; }
.log-tab { padding: 5px 10px; border-radius: 8px; color: var(--app-muted); font-size: 11.5px; cursor: pointer; border: none; background: transparent; }
.log-tab:hover { background: var(--app-hover); color: var(--app-fg); }
.log-tab.active { background: var(--app-hover); color: var(--app-fg); font-weight: 600; }
.log-stream { flex: 1; overflow: auto; padding: 9px 14px; font: 400 11.5px/1.85 ui-monospace, Menlo, Monaco, monospace; }
.log-line { display: flex; gap: 12px; }
.log-line .ts { color: var(--app-faint); flex: none; }
.log-line .lvl { min-width: 52px; flex: none; font-weight: 600; }
.log-line .msg { color: var(--app-muted); }
.lvl-info { color: var(--ok); }
.lvl-warn { color: var(--warn); }
.lvl-error { color: var(--err); }
.lvl-agent { color: var(--info); }
.lvl-debug { color: var(--app-faint); }
.caret {
  display: inline-block; width: 7px; height: 12px; margin-left: 5px;
  background: var(--dot-info); vertical-align: -2px; animation: pulseDot 1s ease-in-out infinite;
}

/* ---------- misc components ---------- */
.list-row {
  display: flex; align-items: center; gap: 12px; padding: 11px 14px;
  border-bottom: 1px solid var(--app-line-soft);
}
.list-row:last-child { border-bottom: none; }
.list-row:hover { background: var(--app-hover); }

.bar-chart { display: flex; align-items: flex-end; gap: 6px; height: 132px; }
.bar-chart .bar { flex: 1; border-radius: 5px 5px 2px 2px; background: linear-gradient(180deg, var(--accent), var(--accent-2)); opacity: .85; }
.bar-chart .bar.alt { background: linear-gradient(180deg, var(--dot-ok), var(--dot-ok)); opacity: .55; }

/* ---------- spend / ACOS combo chart ----------
   Daily spend as bars, ACOS as a line over the same days. The line lives in an
   SVG stretched to the plot area, so its geometry is a plain 0-100 grid the
   server can compute; non-scaling strokes keep it from being smeared by that
   stretch, and the point markers are HTML so they stay round. */
.chart { display: flex; gap: 10px; }
.chart-scale { display: flex; flex-direction: column; justify-content: space-between; flex: none; }
.chart-scale span { font: 500 9.5px ui-monospace, Menlo, monospace; color: var(--app-faint); line-height: 1; }
.chart-scale.right { text-align: right; }
.chart-main { flex: 1; min-width: 0; }
.chart-plot { position: relative; height: 168px; }

.chart-gridline { position: absolute; left: 0; right: 0; border-top: 1px solid var(--app-line-soft); }

.chart-bars { position: absolute; inset: 0; display: flex; align-items: flex-end; gap: 3px; }
.chart-col { flex: 1; min-width: 0; height: 100%; display: flex; align-items: flex-end; }
.chart-bar {
  width: 100%; border-radius: 3px 3px 0 0; min-height: 1px;
  background: linear-gradient(180deg, var(--accent), var(--accent-2)); opacity: .8;
}
.chart-col:hover .chart-bar { opacity: 1; }
/* A day the feed has no row for is not a day of zero spend, and should not be
   drawn as a flat bar that looks like one. */
.chart-col.is-empty { background: repeating-linear-gradient(45deg, var(--app-line-soft) 0 4px, transparent 4px 8px); }

/* The advertising day still running. Drawn hollow rather than filled: a few
   hours of spend beside whole days would otherwise read as a collapse. */
.chart-col.is-today .chart-bar {
  background: none;
  border: 1.5px dashed var(--accent);
  border-bottom: none;
  border-radius: 3px 3px 0 0;
  opacity: .95;
}
.chart-dot.is-today { background: var(--app-card); border: 2px solid var(--dot-ok); box-shadow: none; }

.chart-line { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.chart-acos { fill: none; stroke: var(--dot-ok); stroke-width: 2; vector-effect: non-scaling-stroke; stroke-linejoin: round; }
.chart-target { stroke: var(--warn); stroke-width: 1.5; stroke-dasharray: 5 4; vector-effect: non-scaling-stroke; }
.chart-dot {
  position: absolute; width: 6px; height: 6px; margin: -3px 0 0 -3px; border-radius: 50%;
  background: var(--dot-ok); box-shadow: 0 0 0 2px var(--app-panel);
}
.chart-axis { display: flex; gap: 3px; margin-top: 7px; }
.chart-axis span {
  flex: 1; min-width: 0; text-align: center; overflow: hidden;
  font: 500 9.5px ui-monospace, Menlo, monospace; color: var(--app-faint); white-space: nowrap;
}
.chart-legend { display: flex; align-items: center; gap: 12px; font-size: 11px; color: var(--app-muted); }
.legend-key { width: 9px; height: 3px; border-radius: 2px; flex: none; }
.legend-gap {
  height: 9px;
  background: repeating-linear-gradient(45deg, var(--app-line-soft) 0 3px, transparent 3px 6px);
  border: 1px solid var(--app-line-soft);
}
.legend-today { height: 9px; background: none; border: 1.5px dashed var(--accent); border-bottom: none; }

/* ---------- metric strip ---------- */
.metric-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); }
.metric-cell { padding: 11px 14px; border-right: 1px solid var(--app-line-soft); min-width: 0; }
.metric-cell:last-child { border-right: none; }
.metric-cell .m-label { font-size: 10.5px; color: var(--app-faint); text-transform: uppercase; letter-spacing: .04em; }
.metric-cell .m-value { font: 600 15px/1.3 ui-monospace, Menlo, monospace; letter-spacing: -.02em; margin-top: 3px; }

/* One bar split into its parts -- raised / lowered / held. */
.split-bar { display: flex; height: 8px; border-radius: 6px; overflow: hidden; background: var(--app-hover); }
.split-bar > span { display: block; height: 100%; }

/* ---------- lazy panel placeholders ----------
   Same height as the panel they stand in for, so the page does not jump when
   the real content lands. */
.skeleton { background: var(--app-card-2); border-radius: 8px; }
.skeleton-pulse { animation: skeletonFade 1.4s ease-in-out infinite; }
@keyframes skeletonFade { 0%, 100% { opacity: .45; } 50% { opacity: .8; } }
@media (prefers-reduced-motion: reduce) { .skeleton-pulse { animation: none; } }

.progress { background: var(--app-hover); height: 5px; border-radius: 6px; }
.progress-bar { background: linear-gradient(90deg, var(--accent), var(--accent-2)); }

.offcanvas, .modal-content { background: var(--app-panel); color: var(--app-fg); border-color: var(--app-line); }
.offcanvas-header, .modal-header, .modal-footer { border-color: var(--app-line-soft); }
.dropdown-menu {
  background: var(--app-card); border: 1px solid var(--app-line); font-size: 12.5px;
  border-radius: 12px; padding: 5px;
}
.dropdown-item { color: var(--app-muted); border-radius: 8px; padding: 6px 10px; }
.dropdown-item:hover, .dropdown-item:focus { background: var(--app-hover); color: var(--app-fg); }
.dropdown-divider { border-color: var(--app-line-soft); }
.nav-tabs { border-bottom: 1px solid var(--app-line); gap: 2px; }
.nav-tabs .nav-link {
  border: none; color: var(--app-muted); font-size: 12.5px; padding: 7px 12px;
  border-radius: 9px 9px 0 0;
}
.nav-tabs .nav-link:hover { color: var(--app-fg); background: var(--app-hover); }
.nav-tabs .nav-link.active {
  background: transparent; color: var(--app-fg); font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--accent);
}

.htmx-indicator { opacity: 0; transition: opacity .15s ease; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { opacity: 1; }
.htmx-swapping { opacity: .35; transition: opacity .1s ease; }

/* An empty panel is a state, not a failure, and it is what most of this app
   looks like before the first run. The glyph gets a tile of its own so the
   block has a centre of gravity instead of reading as a stray icon. */
/* ---------- inline form notice ----------
   The reason a submission was refused, shown where the correction is made. Not
   a toast: a toast is for something that happened elsewhere and it leaves
   before a form is retyped. */
.form-notice {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 9px 11px; border-radius: 10px;
  font-size: 11.5px; line-height: 1.55;
  border: 1px solid transparent;
}
.form-notice i { font-size: 12px; line-height: 1.5; flex: none; }
.form-notice-err { background: var(--err-bg); border-color: var(--err); color: var(--err); }
.form-notice-ok { background: var(--ok-bg); border-color: var(--dot-ok); color: var(--ok); }

.empty-state { padding: 40px 20px; text-align: center; color: var(--app-muted); }
.empty-state i {
  font-size: 20px; color: var(--app-faint); margin: 0 auto 12px;
  width: 46px; height: 46px; border-radius: 14px;
  display: flex; align-items: center; justify-content: center;
  background: var(--app-card-2); border: 1px solid var(--app-line-soft);
}

/* ---------- auth ---------- */
.auth-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; }
.auth-card { width: 100%; max-width: 380px; }

/* The sign-in card is the only object on the page it sits on, so it carries the
   elevation rather than borrowing the flat panel treatment used inside the app,
   where a dozen panels share the screen and none may shout. */
.auth-card .panel { border-radius: 16px; }
.auth-card .panel-body { padding: 22px 22px 24px; }
[data-bs-theme="dark"] .auth-card .panel {
  box-shadow: 0 24px 60px -24px rgba(0, 0, 0, .85), 0 0 0 1px rgba(255, 255, 255, .03);
}
[data-bs-theme="light"] .auth-card .panel { box-shadow: 0 18px 45px -22px rgba(16, 18, 24, .28); }

/* Auth inputs are the one place in the product where a control is the whole
   task rather than one of thirty on a dense page, so they get room. */
.auth-card .form-control { min-height: 40px; }
.auth-card .btn-accent { min-height: 42px; }

/* ---------- landing split (index) ---------- */
.landing-shell { min-height: 100vh; display: flex; }
.landing-left {
  flex: 1 1 58%; display: flex; flex-direction: column; justify-content: center;
  gap: 30px; padding: 56px clamp(28px, 6vw, 88px); max-width: 720px;
  position: relative;
}

/* A single soft accent wash behind the headline. The page was two near-black
   rectangles divided by a hairline; this gives the left column a light source
   so the split reads as deliberate. Pointer-transparent and behind everything,
   so it changes nothing but the backdrop. */
.landing-left::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(60% 55% at 12% 18%, var(--accent-soft), transparent 70%),
    radial-gradient(45% 45% at 85% 90%, oklch(.62 .15 250 / .12), transparent 70%);
}

.landing-right {
  flex: 1 1 42%; display: flex; align-items: center; justify-content: center;
  padding: 40px; background: var(--app-panel); border-left: 1px solid var(--app-line);
}
.landing-feature { display: flex; align-items: flex-start; gap: 12px; }

/* The four feature rows are a list, and reading one should not require finding
   where the previous one ended. */
.landing-feature { padding: 10px 12px; margin: 0 -12px; border-radius: 12px; transition: background .15s ease; }
.landing-feature:hover { background: var(--app-card-2); }

/* ---------- responsive ---------- */
@media (max-width: 991.98px) {
  .app-sidebar { position: fixed; top: 54px; bottom: 0; left: 0; z-index: 50; transform: translateX(-100%); }
  .app-sidebar.mobile-open { transform: none; }
  .inspector { position: fixed; top: 54px; bottom: 0; right: 0; z-index: 45; width: 300px; transform: translateX(100%); transition: transform .18s ease; }
  .inspector.mobile-open { transform: none; }
  .search-pill, .switch-group, .crumb { display: none !important; }
  .landing-shell { flex-direction: column; }
  .landing-left { max-width: none; padding: 40px 24px; }
  .landing-right { border-left: none; border-top: 1px solid var(--app-line); }
}
