/* DigiBot -- dashboard styles. No build step, no framework.
 *
 * Four decisions worth knowing before changing anything here.
 *
 * 1. The app is a rounded card floating on a tinted ground, not a window
 *    filling the viewport. That is where most of the "product rather than
 *    admin template" feeling comes from, and it costs one padded body and
 *    one border-radius.
 *
 * 2. The sidebar is light, and the active item is a soft tinted pill rather
 *    than a solid block. A dark rail reads as a developer tool; this reads as
 *    something a salesperson is happy to have open all day. The eye still
 *    gets its anchor, from the rule between the rail and the workspace.
 *
 * 3. Depth comes from shadow, not from outlines. Cards have no hairline
 *    border -- a border plus a shadow is how an interface starts looking
 *    busy at the exact moment it has real data in it. Borders are kept for
 *    the things that genuinely divide: table rows, the rail, inputs.
 *
 * 4. Everything is driven by the custom properties below. Changing the
 *    palette should mean editing `:root`, never hunting for a hex code --
 *    and the dark theme is the same file redefining the same names, so a
 *    colour that only exists inside the media query is a bug waiting for
 *    someone to switch themes.
 *
 * Class names are load-bearing. `app.js` queries `.tab`, `.pane`,
 * `.is-active`, `.ds-row`, `.name`, `.spacer` and the ids throughout.
 * Renaming one silently breaks a screen -- the JS does not throw, the
 * element simply never appears.
 */

:root {
  /* The ground the app card sits on. Tinted rather than grey: it is the one
     surface with no content on it, so it is where colour costs nothing and
     buys the whole impression. */
  --page: #dcd7f5;

  /* Surfaces, lightest to deepest. --raised is for things sitting *on* a
     card (code blocks, table heads) and must stay distinguishable from
     --surface without a border doing all the work. */
  --bg: #faf9fe;
  --surface: #ffffff;
  --raised: #f5f3fc;
  --sidebar: #fbfaff;
  --sidebar-hover: #f1eefc;
  --sidebar-text: #55537a;

  --border: #ecebf7;
  --border-strong: #dcd8ef;

  --text: #16143a;
  --muted: #74729a;
  --faint: #9b99ba;

  /* White on this measures 5.8:1, which clears AA for the 14px button text
     and the active nav item -- neither qualifies for the large-text
     allowance. Brightening it is the obvious tweak and the one that breaks
     that, so check before changing. */
  --accent: #5b4be8;
  --accent-hover: #4c3cd9;
  --accent-soft: #eeebfe;
  --accent-text: #4c3cd9;

  --ok: #0f7a4d;
  --ok-soft: #e4f6ed;
  --warn: #9a5b00;
  --warn-soft: #fdf2e2;
  --err: #be2b21;
  --err-soft: #fdecea;

  /* Generous, and consistent: the card radius is what the eye reads as the
     product's "shape", so one value everywhere beats five tuned ones. */
  --radius-lg: 22px;
  --radius: 16px;
  --radius-sm: 11px;
  --radius-pill: 999px;

  /* Soft and diffuse rather than tight. These sit on a tinted ground where
     a hard shadow reads as a sticker; the lift wants to be felt, not seen. */
  --shadow-sm: 0 1px 2px rgba(32, 25, 85, 0.05);
  --shadow: 0 2px 6px rgba(32, 25, 85, 0.05), 0 12px 28px -12px rgba(32, 25, 85, 0.14);
  --shadow-lg: 0 4px 10px rgba(32, 25, 85, 0.07), 0 28px 60px -20px rgba(32, 25, 85, 0.28);

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  /* Served from Google Fonts in index.html, with the system stack behind it
     so a blocked or offline load degrades to something real rather than to
     Times. */
  --sans: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --page: #08070f;
    --bg: #0f0e18;
    --surface: #171624;
    --raised: #1e1d2e;
    --sidebar: #141322;
    --sidebar-hover: #221f39;
    --sidebar-text: #a5a2c4;

    --border: #272538;
    --border-strong: #37344c;

    --text: #eceaf6;
    --muted: #9b98bd;
    --faint: #706d90;

    /* White on this measures 4.95:1. The brighter violet that reads better
       on a dark ground (#7c74ff) measures 3.62 and fails, so this one
       stays. --accent-text below is the light counterpart, for accent
       text on a dark surface, which is the opposite problem. */
    --accent: #6358f5;
    --accent-hover: #7168ff;
    --accent-soft: #211d44;
    --accent-text: #a9a2ff;

    --ok: #4ec98c;
    --ok-soft: #10291d;
    --warn: #e8b06a;
    --warn-soft: #2a2010;
    --err: #ff8b82;
    --err-soft: #2e1615;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.45);
    --shadow: 0 2px 6px rgba(0, 0, 0, 0.4), 0 14px 30px -12px rgba(0, 0, 0, 0.65);
    --shadow-lg: 0 4px 10px rgba(0, 0, 0, 0.5), 0 30px 60px -20px rgba(0, 0, 0, 0.75);
  }
}

* { box-sizing: border-box; }

/* The `hidden` attribute works through the UA stylesheet's [hidden] rule, and
   any author `display:` beats it. Without this, `.auth { display: flex }` kept
   the sign-in card on screen permanently -- login succeeded, the app loaded
   underneath, and nothing appeared to happen. Same trap for `.checkbox`. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  /* The ground, not the workspace: the app card paints --bg over itself.
     Without this the card and its surround are the same colour and the
     radius has nothing to show against. */
  background: var(--page);
  color: var(--text);
  font: 400 14.5px/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3 { letter-spacing: -0.028em; font-weight: 800; }
.page-head h2 { font-size: 27px; line-height: 1.2; }

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

/* A single focus treatment everywhere. Keyboard users should never have to
   guess where they are, and :focus-visible keeps it off mouse clicks. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

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

/* The app is a card on a tinted ground. At narrow widths the margin and the
   radius both go: a rounded card with 8px of ground around it looks like a
   mistake rather than a choice. */
#app {
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - 32px);
  margin: 16px;
  background: var(--bg);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

@media (max-width: 860px) {
  #app { margin: 0; border-radius: 0; min-height: 100vh; }
}

/* Full width, above the rail rather than beside it, so the brand sits over
   the nav it belongs to and the page title lines up with the content. */
.topbar {
  display: flex; align-items: center; gap: 16px;
  padding: 0 24px 0 0;
  height: 68px;
  flex: none;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.sidebar-brand {
  display: flex; align-items: center; gap: 11px;
  width: 244px;
  flex: none;
  padding: 0 20px;
  height: 100%;
  color: var(--text);
  font-weight: 800; font-size: 17px; letter-spacing: -0.025em;
}
.sidebar-brand .logo {
  width: 30px; height: 30px; border-radius: 10px;
  background: linear-gradient(145deg, var(--accent) 0%, #9d7bff 100%);
  box-shadow: 0 4px 10px -3px rgba(91, 75, 232, 0.6);
  flex: none;
}

.topbar-title {
  margin: 0;
  font-size: 19px; font-weight: 700; letter-spacing: -0.025em;
}

.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }

.org-chip {
  max-width: 180px;
  font-size: 13px; font-weight: 600; color: var(--muted);
  padding: 7px 13px;
  border-radius: var(--radius-pill);
  background: var(--raised);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The rail and the workspace, under the bar. */
.app-body { display: flex; flex: 1; min-height: 0; }

.sidebar {
  width: 244px;
  flex: 0 0 244px;
  background: var(--sidebar);
  color: var(--sidebar-text);
  display: flex;
  flex-direction: column;
  padding: 16px 14px 18px;
  gap: 18px;
  border-right: 1px solid var(--border);
}

.sidebar .tabs { display: flex; flex-direction: column; gap: 3px; }

@media (max-width: 860px) {
  .app-body { flex-direction: column; }
  .sidebar {
    width: auto; flex: none;
    border-right: 0; border-bottom: 1px solid var(--border);
  }
  .sidebar .tabs { flex-direction: row; overflow-x: auto; }
  .sidebar-brand { width: auto; }
  .topbar-title { display: none; }
}

.shell { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: auto; }

main { padding: 28px 30px 60px; max-width: 1280px; width: 100%; }
.pane { display: none; animation: rise 0.22s ease both; }
.pane.is-active { display: block; }

/* Enough to signal "this is new content" and not enough to be noticed as an
   animation. Anything longer reads as slowness on a tab switch. */
@keyframes rise {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .pane { animation: none; }
  * { transition: none !important; }
}

.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }

@media (max-width: 1000px) { .grid { grid-template-columns: 1fr; } }

@media (max-width: 860px) {
  .topbar { height: auto; padding: 10px 14px; flex-wrap: wrap; }
  main { padding: 20px 16px 48px; }
}

/* ====================== nav ====================== */

/* The sidebar holds two products, not eleven features.
 *
 * One answers the organization's own staff from its database; the other
 * answers strangers arriving on WhatsApp. That is not a presentational
 * grouping -- it is `AgentAudience`, which decides whether the data tool is
 * reachable at all. A flat list made a developer ask which half was which,
 * which is a fair warning about what it does to a customer.
 *
 * A `<p>` rather than a heading element: these label groups inside a nav
 * that already has an aria-label, and an <h3> here would put rungs in the
 * document outline that lead nowhere.
 */
.tab-group {
  margin: 18px 0 6px;
  padding: 0 13px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sidebar-muted, var(--muted));
  opacity: 0.75;
}
.tab-group:first-of-type { margin-top: 10px; }

.tab {
  display: flex; align-items: center; gap: 11px;
  width: 100%;
  padding: 10px 13px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: inherit;
  font: 550 14px/1.2 var(--sans);
  text-align: left;
  cursor: pointer;
  transition: background 0.14s ease, color 0.14s ease;
}
.tab .ico { width: 18px; height: 18px; flex: none; opacity: 0.75; }
.tab:hover { background: var(--sidebar-hover); color: var(--text); }

/* Tinted rather than filled. A solid indigo block for the current section
   shouts louder than anything on the page it is pointing at. */
.tab.is-active {
  background: var(--accent-soft);
  color: var(--accent-text);
  font-weight: 700;
}
.tab.is-active .ico { opacity: 1; }

@media (max-width: 860px) {
  .tab { width: auto; white-space: nowrap; }
}

/* ====================== cards ====================== */

.card {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 20px 22px;
  margin-bottom: 18px;
}
.card h2 {
  margin: 0 0 6px;
  font-size: 15.5px; font-weight: 620;
}
.muted { color: var(--muted); margin: 0 0 16px; font-size: 13.5px; }
.hint { color: var(--muted); font-size: 12.5px; margin: 6px 0 10px; min-height: 1em; }
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.row-between { justify-content: space-between; }

/* ====================== controls ====================== */

label { display: block; margin-bottom: 14px; font-weight: 550; font-size: 13px; }

.input, .select, .textarea {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 11px 13px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  transition: border-color 0.14s, box-shadow 0.14s;
}
.select { width: auto; margin-top: 0; padding-right: 30px; cursor: pointer;
  /* The native arrow differs per platform and looks pasted on beside a
     custom control, so it is replaced with one drawn in the border colour. */
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
                    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 16px) 51%, calc(100% - 11px) 51%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.textarea { font-family: inherit; resize: vertical; line-height: 1.6; }
.mono { font-family: var(--mono); font-size: 12.5px; }

.input::placeholder, .textarea::placeholder { color: var(--faint); }

.input:hover, .select:hover, .textarea:hover { border-color: var(--muted); }
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--accent);
  /* A soft ring rather than a hard outline: at 3px it reads as the field
     being live without shifting anything around it. */
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.checkbox { display: flex; gap: 10px; align-items: flex-start; font-weight: 400; }
.checkbox input { width: auto; margin: 4px 0 0; accent-color: var(--accent); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 10px 18px;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font: 650 14px/1 var(--sans);
  cursor: pointer;
  transition: background 0.14s, border-color 0.14s, box-shadow 0.14s, transform 0.08s;
}
.btn:active { transform: translateY(0.5px); }
.btn-sm { padding: 7px 14px; font-size: 13px; }

.btn-primary {
  background: var(--accent);
  color: #fff;
  box-shadow: 0 4px 12px -3px rgba(91, 75, 232, 0.55);
}
.btn-primary:hover { background: var(--accent-hover); }

.btn-ghost {
  background: var(--surface); color: var(--text); border-color: var(--border-strong);
}
.btn-ghost:hover { background: var(--raised); border-color: var(--muted); }

/* For the one action that destroys data rather than changing a status.
 *
 * Outlined rather than filled: a solid red block beside four ordinary
 * buttons pulls the eye to the thing nobody should click by reflex, and a
 * row of actions where the dangerous one is the most prominent is a row
 * somebody misclicks. It reads as different, not as the primary choice. */
.btn-danger {
  background: var(--surface); color: var(--err); border-color: var(--err);
}
.btn-danger:hover { background: var(--err-soft); border-color: var(--err); }

.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }

.pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 11px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--raised);
  color: var(--muted);
  font-size: 12px; font-weight: 650;
  white-space: nowrap;
}
.pill-ok { background: var(--ok-soft); color: var(--ok); border-color: transparent; }
.pill-warn { background: var(--warn-soft); color: var(--warn); border-color: transparent; }
.pill-err { background: var(--err-soft); color: var(--err); border-color: transparent; }

.code {
  background: var(--raised); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 14px;
  font-family: var(--mono); font-size: 12px; line-height: 1.6;
  max-height: 340px; overflow: auto; white-space: pre;
  margin: 12px 0 0;
}

/* ====================== connect status ====================== */

.progress {
  height: 7px; background: var(--raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill); overflow: hidden; margin: 16px 0 10px;
}
.progress-bar {
  height: 100%; width: 0;
  background: linear-gradient(90deg, var(--accent), #a78bfa);
  transition: width 0.35s ease;
}
.status {
  margin-top: 16px; padding-top: 16px;
  border-top: 1px solid var(--border);
}
.status-line { margin: 0 0 8px; font-size: 13.5px; }

.report {
  border-radius: var(--radius-sm); padding: 12px 14px; font-size: 13.5px;
  margin-top: 12px; border: 1px solid transparent;
}
.report-ok { background: var(--ok-soft); color: var(--ok); }
.report-err { background: var(--err-soft); color: var(--err); }
.report ul { margin: 8px 0 0; padding-left: 20px; }

/* ====================== lists ====================== */

.ds-list { display: flex; flex-direction: column; gap: 8px; }
.ds-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface);
  transition: border-color 0.14s ease, box-shadow 0.14s ease;
}
.ds-row:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }
.ds-row .name { font-weight: 570; }
.ds-row .spacer { margin-left: auto; }

/* ====================== schema ====================== */

.tbl {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  margin-bottom: 10px; background: var(--surface); overflow: hidden;
}
.tbl > summary {
  padding: 12px 14px; cursor: pointer; display: flex; gap: 10px;
  align-items: baseline; flex-wrap: wrap;
  list-style-position: inside;
}
.tbl > summary:hover { background: var(--raised); }
.tbl > summary::marker { color: var(--faint); }
.tbl-name { font-family: var(--mono); font-weight: 600; font-size: 13px; }
.tbl-desc { color: var(--muted); font-size: 13px; }
.cols { border-top: 1px solid var(--border); padding: 6px 14px 12px; }
.col {
  display: grid; grid-template-columns: 210px 150px 1fr; gap: 12px;
  padding: 7px 0; align-items: center; border-bottom: 1px solid var(--border);
}
.col:last-child { border-bottom: 0; }
.col-name { font-family: var(--mono); font-size: 12.5px; }
.col-type { color: var(--muted); font-size: 12px; font-family: var(--mono); }

.editable {
  font-size: 13px; padding: 4px 8px; border-radius: 6px;
  border: 1px dashed transparent; cursor: text; color: var(--muted);
}
.editable:hover { border-color: var(--border-strong); background: var(--raised); }
.editable:focus {
  outline: none; border: 1px solid var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
  color: var(--text); background: var(--surface);
}

.badge {
  font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: 5px;
  background: var(--accent-soft); color: var(--accent-text); margin-left: 6px;
  font-family: var(--sans); letter-spacing: 0.03em; text-transform: uppercase;
}
.badge-fk { background: var(--warn-soft); color: var(--warn); }

@media (max-width: 760px) { .col { grid-template-columns: 1fr; gap: 3px; } }

/* ====================== chat ====================== */

.chat { display: flex; flex-direction: column; gap: 16px; }
.messages { display: flex; flex-direction: column; gap: 18px; min-height: 240px; }

/* A transcript grows without limit, and one that grows the page pushes the
   composer off the bottom -- so by the fourth exchange you are scrolling the
   whole window to reach the box you type into. Scrolling the transcript
   instead keeps the composer where it was put. */
.messages-scroll {
  overflow-y: auto; max-height: min(62vh, 640px);
  padding-right: 10px; overscroll-behavior: contain;
}

/* Bubbles were capped at 760px, which on a wide panel is a line long enough
   that the eye loses the start of it on the way back. A measure in `ch` caps
   by what is actually being read; the percentage keeps it from filling a
   narrow pane edge to edge, which is what made the two sides hard to tell
   apart. */
.msg {
  display: flex; flex-direction: column; gap: 5px;
  max-width: min(62ch, 78%); align-self: flex-start; align-items: flex-start;
}
.msg-user { align-self: flex-end; align-items: flex-end; }

/* Who said it, and when. Colour and side alone carry this only while you are
   watching it happen live -- coming back to a transcript later, the first
   question is always which of these two is the lead. */
.msg-meta {
  display: flex; gap: 8px; align-items: baseline;
  font-size: 11.5px; color: var(--muted); padding: 0 4px;
}
.msg-who { font-weight: 600; color: var(--text); letter-spacing: 0.01em; }

.bubble {
  padding: 11px 15px; border-radius: 16px 16px 16px 5px;
  border: 1px solid var(--border); background: var(--surface);
  box-shadow: var(--shadow-sm);
  white-space: pre-wrap; overflow-wrap: anywhere;
  line-height: 1.55;
}
/* The user's own message is the accent, filled. It is the one thing on the
   page they wrote, and the asymmetry is what makes a transcript scannable.
   The corner follows the side it sits on, so the tail points at its author. */
.msg-user .bubble {
  background: var(--accent); color: #fff; border-color: transparent;
  border-radius: 16px 16px 5px 16px;
}
.msg-user .bubble a { color: #fff; }
.msg-user .msg-who { color: var(--accent); }

/* Consecutive turns from the same side are one utterance split by the
   model, not two speakers -- so they close up and drop the repeated label. */
.msg + .msg.msg-same { margin-top: -10px; }
.msg-same .msg-meta { display: none; }

.bubble table {
  border-collapse: collapse; margin: 10px 0; font-size: 13px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  overflow: hidden;
}
.bubble th, .bubble td { border-bottom: 1px solid var(--border); padding: 7px 11px; text-align: left; }
.bubble th { background: var(--raised); font-weight: 600; }
.bubble tr:last-child td { border-bottom: 0; }
.bubble code {
  font-family: var(--mono); font-size: 12.5px;
  background: var(--raised); padding: 1px 5px; border-radius: 5px;
}

.steps { font-size: 12.5px; }
.steps > summary { cursor: pointer; color: var(--muted); padding: 2px 0; }
.steps > summary:hover { color: var(--text); }
.step-body {
  border-left: 2px solid var(--border); margin-top: 8px;
  padding-left: 12px; display: flex; flex-direction: column; gap: 10px;
}
.step-sql {
  font-family: var(--mono); font-size: 11.5px; white-space: pre-wrap;
  background: var(--raised); border: 1px solid var(--border);
  padding: 10px; border-radius: var(--radius-sm); margin: 0;
  overflow-x: auto;
}
.step-meta { color: var(--muted); font-size: 11.5px; }
.step-rejected { color: var(--warn); }

.thinking { color: var(--muted); font-style: italic; font-size: 13.5px; }
.err-line { color: var(--err); font-size: 13.5px; }

.composer {
  display: flex; gap: 10px; align-items: flex-end;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 10px;
  box-shadow: var(--shadow-sm);
  position: sticky; bottom: 0;
}
.composer .textarea,
.composer .input { flex: 1; border: 0; margin: 0; background: transparent; }
.composer .textarea:hover { border-color: transparent; }
.composer .textarea:focus { box-shadow: none; }
.composer:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

.rows-table { overflow-x: auto; }

/* ====================== auth ====================== */

.auth {
  min-height: 100vh;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  /* A quiet wash rather than flat grey. The sign-in screen is the first
     thing anyone sees and it is otherwise one box on an empty page. */
  background:
    radial-gradient(900px 500px at 15% -5%, var(--accent-soft), transparent 60%),
    radial-gradient(700px 420px at 95% 105%, var(--accent-soft), transparent 55%),
    var(--bg);
}
.auth-card {
  width: 100%; max-width: 420px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 32px;
  box-shadow: var(--shadow-lg);
}
.auth-card .brand { font-size: 23px; font-weight: 680; margin: 0 0 6px; }
.tabs-auth {
  display: flex; gap: 4px; margin: 22px 0 20px;
  background: var(--raised); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 4px;
}
/* A segmented control, not sidebar nav -- so these override .tab entirely
   rather than inheriting its dark-background colours. */
.tabs-auth .tab {
  flex: 1; justify-content: center; color: var(--muted);
  padding: 7px 10px; font-size: 13.5px; border-radius: 6px;
}
.tabs-auth .tab:hover { background: transparent; color: var(--text); }
.tabs-auth .tab.is-active {
  background: var(--surface); color: var(--text);
  box-shadow: var(--shadow-sm); border: 1px solid var(--border);
}
.auth-form label { display: block; margin-bottom: 16px; }
.auth-form .btn { width: 100%; margin-top: 4px; }
.hint-inline { color: var(--muted); font-weight: 400; font-size: 12px; }
#auth-error { margin-top: 16px; min-height: 1em; }

/* ====================== approval ====================== */

/* A turn that stopped for a person to decide. Deliberately loud: the stream
   has paused and nothing else will happen until someone acts. */
.approval {
  margin: 10px 0;
  padding: 14px 16px;
  border: 1px solid var(--warn);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
  background: var(--warn-soft);
}
.approval-title { font-weight: 620; margin-bottom: 8px; color: var(--warn); }
.approval button {
  margin: 10px 8px 0 0;
  padding: 7px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface); color: var(--text);
  font: inherit; font-size: 13.5px; font-weight: 550;
  cursor: pointer;
}
.approval button:disabled { opacity: 0.5; cursor: default; }
.approval-yes { border-color: var(--ok) !important; color: var(--ok) !important; }
.approval-no { border-color: var(--err) !important; color: var(--err) !important; }

/* ====================== settings and leads ====================== */

/* The settings forms put several labelled inputs on one line. Without a
   basis the longest label decides every column's width, which on the
   WhatsApp form means "Verify token" squeezing "Access token" to nothing. */
.row label { display: flex; flex-direction: column; gap: 5px; flex: 1 1 220px; min-width: 0; margin-bottom: 0; }
.row label .hint-inline { font-weight: 400; }
.row label .input, .row label .select { margin-top: 0; }

/* A key is shown once and has to be selectable and fully visible. Breaking
   on any character rather than on words: it is one long token, and a
   container that scrolls horizontally hides the end of the thing the whole
   panel exists to show. */
#key-reveal {
  display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
  border: 1px solid var(--ok); border-left-width: 3px;
}
#key-reveal .mono {
  word-break: break-all; user-select: all;
  background: var(--surface); border: 1px solid var(--border);
  padding: 10px 12px; border-radius: var(--radius-sm); width: 100%;
  color: var(--text);
}

#lead-list .ds-row, #user-list .ds-row, #key-list .ds-row { gap: 10px; flex-wrap: wrap; }

/* Forms inside a settings card sit under a heading rather than floating in
   a row of their own, so they get a rule above them to group what follows. */
#user-form, #key-form, #lead-search { padding-bottom: 14px; }
#wa-form { border-top: 1px solid var(--border); padding-top: 16px; margin-top: 14px; }
#wa-current:not(:empty) { margin-bottom: 4px; }

/* ====================== panel patterns ======================
   What a service panel is made of, and what this did not have: a page
   header carrying the one thing you came to do, a table with column
   headings rather than a stack of rows, an empty state that tells a new
   customer what to do next, and a dialog for creating things.

   The form-above-the-list shape these replace had three problems. It
   occupied the top of every page whether or not you were creating
   anything; it made "edit" mean "scroll up and look at a form that changed
   underneath you"; and a flex row of unlabelled values is not a table --
   you cannot tell which column you are reading.
   ========================================================= */

.page-head {
  display: flex; align-items: flex-start; gap: 16px;
  margin-bottom: 20px;
}
.page-head-text { min-width: 0; }
.page-head h2 { margin: 0 0 4px; }
.page-head .muted { margin: 0; }
.page-head-actions { margin-left: auto; display: flex; gap: 8px; flex: none; }

/* A filter strip under the header. Distinct from .row so it can gain a
   border or a background later without touching every other row. */
.toolbar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 14px;
}
.toolbar .input { width: auto; min-width: 220px; margin-top: 0; flex: 0 1 280px; }
.toolbar .spacer { margin-left: auto; }

/* A segmented filter. The active one is filled, so which view you are
   looking at is readable without comparing three near-identical buttons. */
.seg { display: inline-flex; background: var(--raised); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 3px; gap: 2px; }
.seg button {
  border: 0; background: none; color: var(--muted);
  padding: 5px 12px; border-radius: 6px; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 550;
}
.seg button:hover { color: var(--text); }
.seg button.is-active { background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm); }

/* ---------- tables ---------- */

.table-wrap {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow: auto;
}
.dg-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.dg-table thead th {
  padding: 13px 18px;
  text-align: left;
  font-size: 12px; font-weight: 700;
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--faint);
  background: transparent;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.dg-table tbody td {
  padding: 15px 18px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.dg-table tbody tr:last-child td { border-bottom: 0; }
.dg-table tbody tr:hover { background: var(--raised); }
/* Rows that open something say so. The hover tint alone reads as
   decoration until you have already guessed. */
.dg-table tbody tr.is-clickable { cursor: pointer; }
.dg-table .num { text-align: right; font-variant-numeric: tabular-nums; }
/* The actions column. Right-aligned and sized to its content, so the
   columns that carry meaning get the width. */
.dg-table .actions { text-align: right; white-space: nowrap; width: 1%; }
.dg-table .actions .btn + .btn { margin-left: 6px; }
.dg-table .primary-cell { font-weight: 570; }
.dg-table .sub { display: block; color: var(--muted); font-size: 12.5px; margin-top: 2px; }

@media (max-width: 760px) {
  .table-wrap { overflow-x: auto; }
  .dg-table { min-width: 560px; }
}

/* ---------- empty states ---------- */

/* A blank table is indistinguishable from a broken one. This says which it
   is, and what to do about it. */
.empty { padding: 40px 20px; text-align: center; color: var(--muted); }
.empty-title { font-weight: 570; color: var(--text); margin-bottom: 4px; }
.empty .btn { margin-top: 14px; }

/* ---------- dialogs ---------- */

/* A panel against the right edge, not a box in the middle.
 *
 * Every form here is a sequence of fields, and several are long -- the agent
 * editor and the lead type editor both run past a centred box's height on a
 * laptop, which is why the old rule grew a `max-height: 700px` media query to
 * rescue them. A panel is the full height of the window at every size, so
 * that case stops being special.
 *
 * It also keeps the table behind it visible. "Which lead type am I editing"
 * is answerable by glancing left, and a centred box covering the row you
 * clicked is the thing that makes a dialog feel like it interrupted you.
 *
 * `margin-left: auto` with a zero top and bottom is what moves it: a dialog
 * is centred by `margin: auto` on all four sides, so overriding three of them
 * pins it right and stretches it.
 */
dialog.modal {
  padding: 0; border: 0;
  border-radius: var(--radius-lg) 0 0 var(--radius-lg);
  background: var(--surface); color: var(--text);
  width: min(600px, 100vw);
  max-width: 100vw;
  height: 100dvh;
  max-height: 100dvh;
  margin: 0 0 0 auto;
  box-shadow: var(--shadow-lg);

  /* Head and foot stay put, the fields scroll between them. Unconditional
   * now rather than under a height media query, because a panel is always
   * exactly as tall as the window. */
  flex-direction: column;

  /* Slides in from the right, and -- because of `allow-discrete` -- slides
   * back out too.
   *
   * Done in CSS rather than by animating in `closeModal`, because there are
   * five ways these close: the X, the Cancel button, a backdrop click,
   * Escape, and `<form method="dialog">` submitting. A JS animation would
   * have to catch all five and would quietly miss one. The browser's own
   * close transition catches them by construction.
   */
  translate: 100% 0;
  transition:
    translate 240ms cubic-bezier(0.22, 0.61, 0.36, 1),
    overlay 240ms allow-discrete,
    display 240ms allow-discrete;
}
dialog.modal[open] { display: flex; translate: none; }

/* The flex column is the form, not the dialog: every one of these wraps its
   head, body and foot in a `<form>`, so a column on the dialog would have a
   single child and the body would never get its scroll. */
dialog.modal > form {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
}

/* The position it animates *from* on open. Without this the panel is simply
   present at its final position on the first frame. */
@starting-style {
  dialog.modal[open] { translate: 100% 0; }
}

dialog.modal::backdrop {
  background: transparent;
  backdrop-filter: blur(2px);
  transition:
    background 240ms ease,
    overlay 240ms allow-discrete,
    display 240ms allow-discrete;
}
dialog.modal[open]::backdrop { background: rgba(10, 11, 16, 0.5); }
@starting-style {
  dialog.modal[open]::backdrop { background: transparent; }
}

/* Full width on a phone, where 600px is wider than the screen and a sliver
   of the table behind it is not worth the cramped form. */
@media (max-width: 640px) {
  dialog.modal { width: 100vw; border-radius: 0; }
}

.modal-head {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 22px 24px 16px;
  border-bottom: 1px solid var(--border);
  flex: none;
}
.modal-head h3 { margin: 0 0 4px; font-size: 16px; font-weight: 620; }
.modal-head .muted { margin: 0; font-size: 13px; }
.modal-close {
  margin-left: auto; border: 0; background: none; cursor: pointer;
  color: var(--muted); font-size: 22px; line-height: 1; padding: 0 2px;
}
.modal-close:hover { color: var(--text); }
/* The only part that scrolls. `min-height: 0` because a flex child defaults
   to min-content, which here is the whole form -- the panel would grow past
   the window instead of the body scrolling inside it. */
.modal-body {
  padding: 18px 22px;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.modal-body label:last-of-type { margin-bottom: 0; }

/* Pinned to the bottom of the panel, so Save is in the same place whether
   the form has three fields or thirty, and reachable without scrolling to
   the end of a long one. */
.modal-foot {
  display: flex; gap: 8px; justify-content: flex-end;
  padding: 16px 22px;
  flex: none;
  border-top: 1px solid var(--border);
  background: var(--surface);
}
.modal-foot .hint { margin: 0 auto 0 0; align-self: center; }

/* A sub-heading inside a pane that holds more than one table -- Settings
   has three. Spaced above rather than below, so it reads as belonging to
   the table that follows it rather than the one before. */
.dg-section {
  margin: 30px 0 14px;
  font-size: 16px; font-weight: 800;
  letter-spacing: -0.02em;
}
.page-head .dg-section { margin-top: 0; }
#pane-settings .page-head + .table-wrap { margin-bottom: 4px; }

/* ---------- binding pickers ---------- */

/* A checkbox list rather than a <select multiple>, which on every platform
   requires ctrl-clicking to pick a second item and silently drops the first
   when somebody does not. Three sources fit; twenty scroll. */
.binds {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 10px 14px 12px; margin: 0 0 14px;
}
.binds legend { padding: 0 6px; font-size: 13px; font-weight: 550; }
.binds-list { display: flex; flex-direction: column; gap: 6px; max-height: 180px; overflow-y: auto; }
.binds-list label {
  display: flex; align-items: center; gap: 9px;
  margin: 0; font-weight: 450; font-size: 13.5px; cursor: pointer;
}
.binds-list input { width: auto; margin: 0; accent-color: var(--accent); }
.binds .hint { margin: 8px 0 0; }
/* ...unless something follows it. The rule above is right for the hint that
   closes a fieldset, where a bottom margin is dead space; it is wrong in the
   middle of one, because `label` carries no top margin -- so the template
   preview sat flush against "Wait before greeting" and the two read as a
   single run-on sentence. */
.binds .hint:not(:last-child) { margin-bottom: 12px; }

/* A hint that is a warning. Not an error -- the configuration is allowed,
   and saying so in red would train people to ignore red. */
.hint-warn { color: var(--warn); font-weight: 500; }

/* A secret with the button that moves it somewhere else. Wrapping is
 * deliberate: a 43-character token clipped by overflow is a token someone
 * copies half of. */
.copyable {
  margin-top: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
}

.copyable .mono {
  margin: 4px 0 8px;
  word-break: break-all;
  user-select: all;
}

/* The inbox: a queue beside a thread.
 *
 * Both columns scroll on their own and the page does not, which is what
 * makes this a screen somebody leaves open rather than one they scroll
 * through. The height is the viewport minus the header and the page title;
 * a fixed pixel height would be wrong on every other display. */
.inbox {
  display: grid;
  grid-template-columns: minmax(260px, 340px) 1fr;
  gap: 16px;
  height: calc(100vh - 230px);
  min-height: 420px;
}

.inbox-list {
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.inbox-row {
  display: block;
  width: 100%;
  text-align: left;
  padding: 11px 13px;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: transparent;
  cursor: pointer;
  font: inherit;
  color: var(--text);
}

.inbox-row:hover { background: var(--raised); }
.inbox-row.is-active { background: var(--accent-soft); }

.inbox-who { font-weight: 600; margin-bottom: 5px; }

.inbox-marks {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-bottom: 5px;
}

/* One line, cut with an ellipsis. A queue row that wraps to six lines stops
 * being scannable, which is the only thing a queue is for. */
.inbox-preview {
  color: var(--muted);
  font-size: 12.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.inbox-thread {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 14px;
}

.inbox-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

.inbox-actions { display: flex; gap: 6px; margin-left: auto; }

.inbox-thread .messages { flex: 1; overflow-y: auto; padding: 14px 2px; }

@media (max-width: 900px) {
  .inbox { grid-template-columns: 1fr; height: auto; }
  .inbox-list { max-height: 280px; }
}

/* The composer's replacement once the 24-hour window shuts. Bordered like
 * the composer it stands in for, so the swap reads as the same control in a
 * different mode rather than as a new panel appearing. */
.template-send {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  background: var(--raised);
}

.template-send .select { margin-bottom: 10px; }

/* The rendered wording, which is the point of the preview: it has to read
 * as the message rather than as a field value. */
#template-preview {
  padding: 9px 11px;
  border-radius: var(--radius-sm);
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  white-space: pre-wrap;
}

/* Home: counters you can act on, and a list of what is not wired.
 *
 * auto-fit rather than a fixed column count, so four cards are four across
 * on a laptop and two on a narrow window without a breakpoint per layout. */
.kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 12px;
  margin-bottom: 26px;
}

.kpi {
  text-align: left;
  font: inherit;
  color: var(--text);
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

/* Only the ones wired to a destination. A card that moves under the cursor
 * and does nothing when clicked is worse than one that sits still. */
button.kpi { cursor: pointer; }
button.kpi:hover { border-color: var(--accent); background: var(--raised); }

.kpi-warn { border-color: var(--warn); background: var(--warn-soft); }
.kpi-err { border-color: var(--err); background: var(--err-soft); }

.kpi-value { font-size: 26px; font-weight: 650; line-height: 1.15; }
.kpi-label { margin-top: 3px; font-weight: 550; }
.kpi-hint { margin-top: 3px; color: var(--muted); font-size: 12.5px; }

.checks {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}

.check {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
}

.check:last-child { border-bottom: 0; }

/* The mark carries the state, so this does not rely on colour alone --
 * a tick and an exclamation read the same to everybody. */
.check-mark {
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 12px;
  font-weight: 700;
  background: var(--warn-soft);
  color: var(--warn);
}

.check.is-ok .check-mark { background: var(--ok-soft); color: var(--ok); }

.check-label { font-weight: 550; }
.check-detail { color: var(--muted); font-size: 12.5px; margin-top: 2px; }

/* The home page as two products side by side.
 *
 * `align-items: start` so a short column does not stretch to match a tall
 * one -- the AI Assistant side genuinely has less to report, and a box of
 * empty space pretending otherwise reads as something failing to load.
 */
.products {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  align-items: start;
}

.product {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding: 18px 18px 16px;
}

.product-head { margin-bottom: 14px; }
.product-head h3 {
  margin: 0 0 3px;
  font-size: 15px; font-weight: 700;
  letter-spacing: -0.01em;
}
.product-head .muted { margin: 0; font-size: 12.5px; }

/* Inside a product card the KPIs are a denser grid than the full-width row
   they used to be, and the checks lose their own border -- the card is
   already the box. */
.product .kpis {
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  margin-bottom: 14px;
}
.product .checks { border: 0; border-radius: 0; background: transparent; }
.product .check { padding-left: 0; padding-right: 0; }
.product .check:last-child { border-bottom: 0; padding-bottom: 0; }

/* One column before the cards get too narrow to read a KPI in. */
@media (max-width: 900px) {
  .products { grid-template-columns: 1fr; }
}

/* A standing warning above a table: leads arrived that nothing is configured
 * for. Warn rather than error -- those leads were answered, by the default
 * brief, so this is "you probably meant something else" and not a failure.
 * The left bar carries the state alongside the colour. */
.notice {
  border: 1px solid var(--border);
  border-left: 3px solid var(--warn);
  border-radius: var(--radius);
  background: var(--warn-soft);
  padding: 11px 14px;
  margin: 0 0 14px;
}

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