:root {
  color-scheme: light;
  --bg: #f7f8f5;
  --panel: #ffffff;
  --ink: #17201b;
  --muted: #66736c;
  --line: #dfe5dd;
  --accent: #1e6f5c;
  --accent-strong: #164f42;
  --notice: #ecf7f2;
  /* The rail needs its own pair rather than borrowing `--accent-strong`.
     That token *inverts* between themes — it is the darker green in light mode
     and the lighter one in dark — which is right for text on a panel and wrong
     for a surface: in dark mode the rail would have gone mint with pale text on
     it. These two only ever get darker. */
  --rail: #164f42;
  --rail-ink: #e8f1ec;
  /* No edge needed in light mode: a dark green column against an off-white
     page separates itself. The token exists so dark mode, where it does not,
     can say so without a second rule. */
  --rail-edge: transparent;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

/* ---- App frame ----
   The rail is a fixed column and the content scrolls beside it, so moving
   between pages does not move the navigation. `100dvh` and not `100vh`: on iOS
   the address bar makes `vh` taller than the visible area, which would push the
   rail's foot — the sign-out button — under the browser chrome. */
.app {
  display: grid;
  grid-template-columns: var(--rail-w, 216px) minmax(0, 1fr);
  min-height: 100dvh;
}

.shell {
  width: min(1320px, calc(100% - 48px));
  margin: 0 auto;
  padding: 28px 0 48px;
  min-width: 0;
}

/* ---- Navigation rail ----

   Deep green, and that is the one deliberate risk in this shell. Almost every
   dashboard rail is neutral grey or white; this app already has a green it uses
   for every accent, so making the frame that same colour at full strength ties
   the chrome to the identity instead of adding a second, quieter one. It also
   does the practical thing a rail must: recede. Bright content, dark frame, and
   the eye goes to the work rather than to the furniture.

   The whole rail is one scroll container so a long admin list cannot push the
   account cluster out of reach on a short window. */
.rail {
  position: sticky;
  top: 0;
  align-self: start;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 18px 12px 14px;
  background: var(--rail);
  color: var(--rail-ink);
  border-right: 1px solid var(--rail-edge);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.rail-mark {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 4px 14px;
  padding: 4px;
  color: #ffffff;
  text-decoration: none;
  font-weight: 700;
  letter-spacing: -0.01em;
}

/* The monogram is the one thing that survives the icons-only rail, so it keeps
   its own box rather than being the first letter of a name that disappears. */
.rail-mark-badge {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.16);
  font-size: 14px;
}

.rail-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rail-group {
  margin: 14px 8px 4px;
  color: color-mix(in srgb, var(--rail-ink) 55%, transparent);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.rail-link {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 10px;
  border-radius: 9px;
  color: color-mix(in srgb, var(--rail-ink) 82%, transparent);
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.2;
}

.rail-link:hover {
  background: rgba(255, 255, 255, 0.08);
  color: #ffffff;
}

/* The current page is a filled row, not a coloured word. In a vertical list the
   thing being asked is "where am I", and a block answers that from the corner
   of the eye where a text-colour change does not. */
.rail-link.current {
  background: rgba(255, 255, 255, 0.14);
  color: #ffffff;
  font-weight: 600;
}

.rail-icon {
  width: 20px;
  height: 20px;
  flex: 0 0 20px;
}

.rail-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The badge's default is the app's green, which was legible on a white top bar
   and is nearly invisible on the rail — a waiting count that cannot be seen is
   the one thing this element exists not to be. Light pill, dark numeral. */
.rail .nav-badge {
  flex: 0 0 auto;
  background: var(--rail-ink);
  color: var(--rail);
}

/* ---- The account cluster ----
   `margin-top: auto` pins it to the foot without a fixed height above it, so
   adding a destination pushes the list down rather than overlapping this. */
.rail-foot {
  margin-top: auto;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.rail-user {
  padding: 0 6px;
  color: #ffffff;
  font-size: 13px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rail-foot-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 4px;
}

.rail-signout {
  background: none;
  border: none;
  padding: 2px;
  color: color-mix(in srgb, var(--rail-ink) 70%, transparent);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  text-decoration: underline;
}

.rail-signout:hover { color: #ffffff; }

.rail .theme-toggle {
  background: rgba(255, 255, 255, 0.1);
  border-color: transparent;
  color: #ffffff;
}

/* Focus has to be visible against the dark rail, where the app's green outline
   would disappear. */
.rail a:focus-visible,
.rail button:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

/* ---- Narrow: icons only ----
   A different bargain rather than a smaller version of the same one. At this
   width somebody is looking for one destination, not reading the list, so the
   labels go and the icons centre. The text stays in the DOM for screen readers
   and becomes the tooltip via `title` on the link. */
@media (max-width: 1100px) {
  .app { --rail-w: 64px; }
  .rail { padding: 18px 8px 14px; }
  .rail-label,
  .rail-mark-name,
  .rail-user,
  .rail-signout { display: none; }
  .rail-link { justify-content: center; padding: 10px 0; }
  .rail-mark { justify-content: center; margin: 0 0 14px; }
  .rail-group {
    margin: 12px 0 4px;
    text-align: center;
    font-size: 0;
    line-height: 0;
  }
  /* The group heading has no room for its word, so it becomes the rule it was
     standing in for. Keeping the element means the grouping survives. */
  .rail-group::after {
    content: "";
    display: block;
    height: 1px;
    margin: 0 10px;
    background: rgba(255, 255, 255, 0.16);
  }
  .rail-foot-actions { justify-content: center; }
  .rail-link .nav-badge {
    position: absolute;
    transform: translate(12px, -12px);
  }
  .rail-link { position: relative; }
}

/* ---- Phone: a strip across the top ----
   A 64px column is a tenth of a phone screen for something used once a page. It
   becomes a scrolling strip instead, and the account cluster rejoins it at the
   end rather than being stranded at the bottom of a horizontal bar. */
@media (max-width: 720px) {
  .app {
    grid-template-columns: minmax(0, 1fr);
  }
  .rail {
    position: static;
    height: auto;
    flex-direction: row;
    align-items: center;
    gap: 2px;
    padding: 8px 10px;
    overflow-x: auto;
    overflow-y: hidden;
  }
  .rail-list { flex-direction: row; }
  .rail-mark { margin: 0 6px 0 0; }
  .rail-group { display: none; }
  .rail-link { padding: 8px 10px; }
  /* The current entry says its name; the rest stay glyphs.
     Labelling all nine would make the strip three screens wide and put every
     destination behind a scroll, and labelling none leaves a touch user with no
     tooltip and no way to confirm where they are. Naming only the page you are
     on costs one entry's width and answers the question the other eight do not
     need to. */
  .rail-link.current .rail-label { display: block; }
  .rail-foot {
    margin: 0 0 0 auto;
    padding: 0 0 0 10px;
    border-top: none;
    border-left: 1px solid rgba(255, 255, 255, 0.14);
    flex-direction: row;
    align-items: center;
  }
  .shell { width: calc(100% - 24px); padding-top: 18px; }
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 20px;
}

.eyebrow {
  margin: 0 0 4px;
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
}

h1,
h2,
p {
  margin-top: 0;
}

/* Links had no base rule at all, so every one of them fell through to the
   browser default — blue, and *purple once visited*. That is what made "Open
   contact" and "merge them" turn violet after one click, next to buttons that
   were the app's green. `:visited` is deliberately the same colour: on an
   internal tool "have I clicked this before" is noise, and the browser will
   not let us style it usefully anyway. Anything that needs its own colour
   (.mini, .chip, .tile, .party-name) is more specific and still wins. */
a {
  color: var(--accent);
}

a:hover {
  color: var(--accent-strong);
}

h1 {
  margin-bottom: 0;
  font-size: 30px;
}

h2 {
  font-size: 18px;
}

.status {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 8px 12px;
  background: var(--panel);
  color: var(--accent-strong);
  font-weight: 700;
}

.panel,
.grid article {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
  padding: 20px;
}

form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr)) auto;
  gap: 12px;
  align-items: end;
}

label {
  display: grid;
  gap: 6px;
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
}

input {
  width: 100%;
  min-height: 42px;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 8px 10px;
  color: var(--ink);
  font: inherit;
}

button {
  min-height: 42px;
  border: 0;
  border-radius: 6px;
  padding: 0 14px;
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-weight: 800;
  cursor: pointer;
}

.call-state {
  margin-top: 16px;
  color: var(--muted);
}

.notice {
  border: 1px solid #b9dfd0;
  border-radius: 8px;
  background: var(--notice);
  color: var(--accent-strong);
  padding: 12px;
}

.notice.error {
  border-color: #e0b8b1;
  background: #fff2ef;
  color: #7a2e22;
}

.grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  margin-top: 16px;
}

.history {
  margin-top: 16px;
}

.readiness {
  margin-top: 16px;
}

.readiness-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}

.readiness-item {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 10px;
  color: var(--muted);
  font-size: 13px;
}

.readiness-item strong {
  color: var(--ink);
}

.readiness-item .ready {
  color: var(--accent-strong);
}

.readiness-item .missing {
  color: #8b3528;
}

table {
  width: 100%;
  border-collapse: collapse;
}

th,
td {
  border-bottom: 1px solid var(--line);
  padding: 10px 8px;
  text-align: left;
  vertical-align: top;
}

th {
  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
}

.muted {
  color: var(--muted);
}

.grid p {
  color: var(--muted);
  line-height: 1.5;
}

@media (max-width: 720px) {
  .topbar,
  form,
  .grid,
  .readiness-grid {
    grid-template-columns: 1fr;
  }

  .topbar {
    align-items: flex-start;
  }
}

/* Auth */
.auth-shell {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.auth-panel {
  width: min(400px, 100%);
}

.auth-panel h1 {
  margin-bottom: 16px;
  font-size: 24px;
}

/* Auth forms are a single vertical stack (the global 2-column `form` grid is
   for the dialer). Full-width fields and button read cleanly in the narrow card. */
.auth-panel form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  align-items: stretch;
}

.auth-panel form button {
  margin-top: 4px;
}

.topbar-user {
  display: flex;
  align-items: center;
  gap: 14px;
}

.logout-form {
  margin: 0;
  display: block;
}

.link-button {
  background: none;
  border: none;
  color: var(--muted);
  font: inherit;
  cursor: pointer;
  padding: 0;
  text-decoration: underline;
}

.link-button:hover {
  color: var(--accent);
}

.link-button.current {
  color: var(--ink);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}

.notice.error {
  background: #fdecec;
  color: #8a1f1f;
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 12px;
  font-size: 14px;
}

/* ---- P2 console: layout ---- */
.layout {
  display: grid;
  grid-template-columns: 360px 1fr;
  gap: 20px;
  align-items: start;
}

/* Users table gets the full width; Add user lives in a modal (see .modal). */
.admin-layout {
  grid-template-columns: minmax(0, 1fr);
}

.users-panel {
  min-width: 0;
  overflow-x: auto;
}

/* Fit the panel instead of forcing a horizontal scroll; the actions cell wraps
   (overriding the history `nowrap`) so the row stays inside the table. */
.users-panel table.users {
  min-width: 0;
}

.users td { vertical-align: middle; }
/* break-word (not anywhere): keep emails whole when they fit; only break a
   genuinely too-long value, without shrinking the column pre-emptively. */
.users td:not(.row-actions) { overflow-wrap: break-word; }

.users td.row-actions {
  white-space: normal;
}

.users td.row-actions .num-inline-form {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin: 4px 4px 4px 0;
}

/* Selects styled to match the input/button system (native ones are too small). */
.add-user-panel select,
.users td.row-actions select {
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--ink);
  background: var(--panel);
  font: inherit;
}

.add-user-panel select {
  width: 100%;
  min-height: 42px;
  padding: 8px 10px;
}

.users td.row-actions select {
  min-height: 30px;
  padding: 2px 8px;
  font-size: 12px;
}

/* Badges never break internally (no "Admi n" / "Activ e"). */
.badge { white-space: nowrap; }

/* Add-user modal (native <dialog>). */
dialog.modal {
  width: min(440px, 92vw);
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel);
  color: var(--ink);
  padding: 22px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
}

dialog.modal::backdrop {
  background: rgba(0, 0, 0, 0.55);
}

.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.modal-head h2 { margin: 0; }

.icon-close {
  min-height: auto;
  padding: 0 4px;
  background: none;
  border: 0;
  color: var(--muted);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}

.icon-close:hover { color: var(--ink); }

.modal-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 4px;
}

.creds code {
  overflow-wrap: anywhere;
}

.user-name {
  font-weight: 600;
}

/* ---- Dialer ---- */
.dialer {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 14px;
}

.dial-display {
  width: 100%;
  font-size: 24px;
  letter-spacing: 1px;
  text-align: center;
  padding: 14px 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: #fbfdfb;
}

.keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}

.key {
  padding: 16px 0;
  font-size: 20px;
  font-weight: 600;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 12px;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}

.key:hover { background: #f0f5f1; border-color: var(--accent); }
.key:active { background: #e4efe9; }

.agent-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  color: var(--muted);
}

.agent-field input {
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: 15px;
}

.call-button {
  padding: 14px;
  font-size: 16px;
  font-weight: 600;
  color: #fff;
  background: var(--accent);
  border: none;
  border-radius: 12px;
  cursor: pointer;
}

.call-button:hover { background: var(--accent-strong); }

.call-state {
  margin-top: 14px;
  padding: 12px;
  border-radius: 10px;
  background: var(--notice);
  color: var(--muted);
  font-size: 14px;
}

/* ---- History ---- */
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.panel-head h2 { margin: 0; }

.search {
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: 14px;
  min-width: 200px;
}

table.history {
  width: 100%;
  min-width: 600px;
  border-collapse: collapse;
  font-size: 14px;
}
.history-panel { min-width: 0; }
#history { overflow-x: auto; }
table.history th {
  text-align: left; padding: 8px 10px;
  border-bottom: 1px solid var(--line);
}
table.history td { padding: 10px; border-bottom: 1px solid #eef2ee; }
table.history .num { font-variant-numeric: tabular-nums; }
table.history .when, table.history .dur {
  color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap;
}
table.history th .tz { color: var(--muted); font-weight: 400; font-size: 12px; }
.empty { padding: 16px 4px; }

/* ---- Badges ---- */
.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
}
.badge.ok { background: #e4f4ec; color: #1e6f5c; }
.badge.neutral { background: #eef1ee; color: #66736c; }
.badge.pending { background: #fdf3e0; color: #9a6b13; }
.badge.fail { background: #fdecec; color: #8a1f1f; }
.badge.dir-out { background: #e9eefb; color: #2a4b9b; }
.badge.dir-in { background: #f0e9fb; color: #5a2a9b; }

/* ---- Availability toggle ---- */
.availability {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 0;
}
.availability .dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: #c2ccc5;
}
.availability.on .dot { background: #2fae6f; box-shadow: 0 0 0 3px #d8f0e3; }
.availability.off .dot { background: #c2ccc5; }
.availability .link-button { text-decoration: none; font-weight: 600; }

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

@media (max-width: 860px) {
  .layout { grid-template-columns: 1fr; }
  .panel-head { flex-direction: column; align-items: stretch; }
  .search { min-width: 0; }
  .topbar { flex-direction: column; }
  /* Tighter gaps so the account controls land on the same wrapped line as the
     tail of the nav, instead of pushing "Sign out" onto a line of its own. */
  .topbar-user { width: 100%; flex-wrap: wrap; gap: 6px 12px; }
}

/* ---- Live call state (WebSocket-pushed) ---- */
.call-state .call-id {
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.call-state.ok { background: #e4f4ec; color: #1e6f5c; }
.call-state.fail { background: #fdecec; color: #8a1f1f; }
.call-state.pending { background: #fdf3e0; color: #9a6b13; }
.call-state.neutral { background: #eef1ee; color: #66736c; }

/* ---- Left-column stack (dialer + settings cards) ---- */
.stack {
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-width: 0;
}

/* ---- Saved numbers ---- */
.num-list {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.num-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 12px;
}
.num-item.active { border-color: var(--accent); background: var(--notice); }
.num-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.num-label { font-weight: 700; font-size: 14px; }
.num-value {
  color: var(--muted);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.num-actions { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
.num-inline-form { display: inline; margin: 0; }
.num-empty { padding: 6px 2px; }
.link-button.danger { color: #a23; }
.link-button.danger:hover { color: #c33; }

.numbers-add { display: flex; flex-direction: column; gap: 10px; align-items: stretch; }
.num-add-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.num-notice { margin: 0 0 12px; font-size: 13px; font-weight: 700; }
.num-notice.ok { color: var(--accent-strong); }
.num-notice.error { color: #a23; }

/* ---- Settings forms (voicemail) ---- */
.settings-form { display: flex; flex-direction: column; gap: 10px; align-items: stretch; }
textarea {
  width: 100%;
  min-height: 84px;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 10px;
  color: var(--ink);
  background: var(--panel);
  font: inherit;
  resize: vertical;
}
.vm-hint { margin: 10px 0 0; font-size: 13px; }

/* ---- Theme toggle ---- */
.theme-toggle {
  min-height: 34px;
  width: 34px;
  padding: 0;
  font-size: 16px;
  line-height: 1;
  background: var(--panel);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 8px;
}
.theme-toggle:hover { border-color: var(--accent); }

/* ---- Dark theme ----
   The console's <head> script sets data-theme from the stored choice or the
   OS preference before paint. The media block gives the login page (no toggle)
   a matching base palette; component overrides live under [data-theme]. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0f130e;
  --panel: #171d15;
  --ink: #e8ede6;
  --muted: #93a099;
  --line: #2a332a;
  --accent: #3fae86;
  --accent-strong: #5fcc9f;
  --notice: #15251d;
  /* Lighter than the page, not darker. In dark mode a rail below `--bg` is
     invisible -- the frame and the content became one flat field, and the only
     thing separating them was the text. */
  --rail: #17231d;
  --rail-ink: #dce7e0;
  --rail-edge: #26332c;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #0f130e;
    --panel: #171d15;
    --ink: #e8ede6;
    --muted: #93a099;
    --line: #2a332a;
    --accent: #3fae86;
    --accent-strong: #5fcc9f;
    --notice: #15251d;
    --rail: #17231d;
    --rail-ink: #dce7e0;
    --rail-edge: #26332c;
  }
}
:root[data-theme="dark"] .dial-display,
:root[data-theme="dark"] .key { background: #1c231a; }
:root[data-theme="dark"] .key:hover { background: #222b20; }
:root[data-theme="dark"] .key:active { background: #283220; }
:root[data-theme="dark"] table.history td { border-bottom-color: #232a22; }
:root[data-theme="dark"] .notice { border-color: #254537; }
:root[data-theme="dark"] .notice.error { background: #2a1714; border-color: #5c2a22; color: #f0b8ae; }
:root[data-theme="dark"] .badge.ok,
:root[data-theme="dark"] .call-state.ok { background: #123326; color: #66d3a5; }
:root[data-theme="dark"] .badge.neutral,
:root[data-theme="dark"] .call-state.neutral { background: #20261f; color: #aab4ac; }
:root[data-theme="dark"] .badge.pending,
:root[data-theme="dark"] .call-state.pending { background: #33280f; color: #e6b866; }
:root[data-theme="dark"] .badge.fail,
:root[data-theme="dark"] .call-state.fail { background: #331616; color: #f0a09a; }
:root[data-theme="dark"] .badge.dir-out { background: #182238; color: #9db6ee; }
:root[data-theme="dark"] .badge.dir-in { background: #241836; color: #c3a6ee; }
:root[data-theme="dark"] .link-button.danger,
:root[data-theme="dark"] .mini.danger { color: #ef9a90; border-color: #5c2a22; }
:root[data-theme="dark"] .num-notice.error { color: #ef9a90; }
:root[data-theme="dark"] .availability.on .dot { box-shadow: 0 0 0 3px #163a2a; }

/* ---- P5 polish: balance, dialer picker, party cell, row actions, pager ---- */

/* Account balance (topbar) */
.balance {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  white-space: nowrap;
}
.bal-label {
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.bal-value { font-weight: 800; font-variant-numeric: tabular-nums; }
.bal-value.ok { color: var(--accent-strong); }
.bal-value.muted { color: var(--muted); font-weight: 600; }
.bal-value.low { color: #b3541e; }

/* Dialer: pick your active/saved mobile */
.agent-field-label { color: var(--muted); font-size: 13px; font-weight: 700; }
/* Two stacked lines so the picker + entry field aren't cramped together. */
.agent-pick-row { display: flex; flex-direction: column; gap: 8px; align-items: stretch; }
.agent-pick-row input { width: 100%; min-width: 0; }
.agent-picker {
  align-self: stretch;
  width: 100%;
  max-width: none;
  min-height: 42px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0 8px;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
}
.agent-hint { margin: 0; font-size: 12px; color: var(--muted); line-height: 1.4; }
.agent-hint strong { color: var(--ink); font-weight: 700; }
.agent-hint.warn { color: #b3541e; }
.agent-hint.warn strong { color: #b3541e; }

/* Dialer: bridge-number reference line */
.dial-hint { margin: -4px 0 12px; font-size: 12px; color: var(--muted); }
.dial-hint strong { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }
.dial-hint.warn { color: #b3541e; }

/* History: counterparty name over number */
.party-name { display: block; font-weight: 700; font-size: 14px; }
.party-num {
  display: block;
  color: var(--muted);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* History: per-row quick actions — Call + Label/Rename stay on one line */
td.row-actions { white-space: nowrap; }
.row-btns { display: flex; flex-wrap: nowrap; gap: 6px; align-items: center; }
.mini {
  min-height: 30px;
  padding: 4px 10px;
  font-size: 12px;
  font-weight: 700;
  border-radius: 6px;
}
.mini.ghost {
  background: transparent;
  color: var(--accent-strong);
  border: 1px solid var(--line);
}
.mini.ghost:hover:not([disabled]) { border-color: var(--accent); }
.mini.danger { color: #a23; border-color: #e0b8b1; }
.mini.danger:hover:not([disabled]) { border-color: #a23; }
.mini[disabled] { opacity: 0.45; cursor: default; }
/* A form whose only visible child is its button (Revoke, Reverse, Drop, Clear
   all). The base `form' rule is a `minmax(0, 1fr) minmax(0, 1fr) auto' grid,
   and `minmax(0, ...)' means the track is allowed to size to nothing: inside a
   table cell the button was laid out about 16px wide and its own label spilled
   out past the green pill. These forms have no fields to align, so they opt out
   of the grid. `action-form' is the name to reach for; the two older aliases
   predate it and keep working because other rules already hook them by name. */
.action-form,
.num-inline-form,
.dupe-form { display: inline; margin: 0; }
/* Label editor: its own full-width row so the name field spans the bottom line */
.label-row[hidden] { display: none; }
.label-row > td { padding: 8px 10px 12px; }
.label-form {
  display: flex;
  gap: 8px;
  align-items: center;
  width: 100%;
  margin: 0;
}
.label-input {
  min-height: 36px;
  padding: 6px 10px;
  font-size: 14px;
  flex: 1 1 auto;
  min-width: 0;
}

/* History: pager */
.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 14px;
}
.pager-page { color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; }

@media (max-width: 860px) {
  .balance { order: -1; }
}

/* iOS shrinks the native <select> and its option list, and zoom-focuses text
   inputs, when their font is under 16px. Hold these at 16px on phones so the
   saved-number picker stays legible and tapping a field doesn't zoom the page. */
@media (max-width: 700px) {
  .agent-picker,
  .agent-field input,
  .search,
  .label-input,
  .add-user-panel select,
  .users td.row-actions select { font-size: 16px; }
}

/* History on a phone: the 6-column table won't fit, so drop the header and
   reflow each row into a stacked card. Party name/number leads its own line and
   the action buttons take the bottom line; direction, status, when and duration
   flow inline between them. The full-width label editor row keeps working. */
@media (max-width: 700px) {
  #history { overflow-x: visible; }
  table.history { min-width: 0; }
  .users-panel table.users { min-width: 0; }
  .users td.row-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
  }
  .users td.row-actions .num-inline-form { margin: 0; }
  table.history thead { display: none; }
  table.history tbody tr:not(.label-row) {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 12px;
    padding: 14px 2px;
    border-bottom: 1px solid var(--line);
  }
  table.history tbody tr:not(.label-row) > td { border: 0; padding: 0; }
  /* Counterparty leads on its own line, larger for legibility on a phone. */
  table.history tbody td.party { flex: 1 1 100%; margin-bottom: 2px; }
  table.history td.party .party-name { font-size: 17px; line-height: 1.25; }
  table.history td.party .party-num { font-size: 15px; }
  /* Direction, status, when and duration share one quiet meta line. */
  table.history tbody td.when,
  table.history tbody td.dur { font-size: 13px; }
  /* Actions get their own bottom line. */
  table.history tbody td.row-actions { flex: 1 1 100%; margin-top: 4px; }
  /* The label editor stays hidden until Label/Rename is tapped — :not([hidden])
     is needed so this rule doesn't out-specify the base [hidden] display:none. */
  table.history tr.label-row:not([hidden]) { display: block; }
  table.history tr.label-row > td { display: block; padding: 6px 2px 10px; }
}

/* Dark theme overrides for the above */
:root[data-theme="dark"] .agent-picker { background: #1c231a; }
:root[data-theme="dark"] .bal-value.low,
:root[data-theme="dark"] .agent-hint.warn,
:root[data-theme="dark"] .agent-hint.warn strong,
:root[data-theme="dark"] .dial-hint.warn { color: #e0975a; }

/* ==================================================================
   Contacts — list, detail, timeline, CSV import
   ================================================================== */

/* The contact list is full width; a contact record puts its form and call
   card beside the timeline. */
.layout.single { grid-template-columns: minmax(0, 1fr); }
.layout.contact-layout { grid-template-columns: 420px minmax(0, 1fr); }

.count {
  margin-left: 6px;
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--notice);
  color: var(--accent-strong);
  font-size: 13px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

/* Wraps rather than shrinks. `flex-shrink: 0` keeps the buttons their own size
   (squashed buttons read as broken), but without wrapping, five of them —
   Archived, Duplicates, Import CSV, Import DMs, New contact — pushed the panel
   wider than the phone and gave the whole page a horizontal scrollbar. */
.head-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  flex-shrink: 0;
}

/* Same reason: the heading and its actions stack instead of overflowing. */
.panel-head { flex-wrap: wrap; }

/* The left half of a panel head: heading plus anything that belongs *to* the
   heading, like view chips. Keeping those out of .head-actions is what stops
   them shifting when a right-hand button appears on one view and not another. */
.head-left {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  min-width: 0;
}
/* The chip row has bottom margin for when it sits on its own line; inside a
   panel head it is already vertically centred. */
.head-left .segment-chips { margin: 0; }

/* Links that should read as buttons (Import CSV, Open). `.mini` is written
   for <button>, so anchors need the box and the underline reset. */
a.mini {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  line-height: 1;
}

/* Search plus the two filter selects. Overrides the global 2-column `form`
   grid, which is shaped for the dialer. */
.contact-filters {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  margin-bottom: 14px;
}

/* Native selects are too small next to our inputs; match the input system. */
.contact-filters select,
.settings-form select,
.note-form select {
  min-height: 42px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 10px;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
}

.contacts td { vertical-align: middle; }
.contacts td:not(.row-actions) { overflow-wrap: break-word; }
.roles-cell { line-height: 1.9; }

/* A contact's name is the link into their record. */
a.party-name { color: var(--ink); text-decoration: none; }
a.party-name:hover { color: var(--accent); text-decoration: underline; }

/* Role checkboxes: a horizontal set, not the stacked `label` grid. */
.roles-field { display: flex; flex-direction: column; gap: 6px; }
.field-label { color: var(--muted); font-size: 13px; font-weight: 700; }
.role-options { display: flex; flex-wrap: wrap; gap: 8px 16px; }
label.role-option {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 6px;
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
}
.role-option input {
  width: auto;
  min-height: auto;
  margin: 0;
  accent-color: var(--accent);
}

/* The new-contact modal carries more fields than the add-user one. */
dialog.contact-modal { width: min(560px, 92vw); }

/* ---- Timeline ---- */
.timeline-panel { min-width: 0; }

.note-form {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr) auto;
  gap: 8px;
  align-items: center;
  margin-bottom: 16px;
}

.timeline { list-style: none; margin: 0; padding: 0; }

.timeline-entry {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 11px 2px;
  border-bottom: 1px solid #eef2ee;
}
.timeline-entry:last-child { border-bottom: 0; }

/* A withdrawn note is struck through and dimmed, never removed. Reading it
   should take deliberate effort while leaving no doubt that it happened. */
.timeline-entry.retracted .entry-body { text-decoration: line-through; }
.timeline-entry.retracted { opacity: 0.62; }
.timeline-entry.retracted .badge { text-decoration: none; }

.entry-when {
  flex: 0 0 auto;
  min-width: 128px;
  color: var(--muted);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.entry-body { flex: 1 1 auto; min-width: 0; overflow-wrap: break-word; }
.entry-dur {
  color: var(--muted);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* Screen-pop link inside the live call-state box. */
.call-contact { color: inherit; font-weight: 700; }

/* ---- CSV import ---- */
.settings-form input[type="file"] {
  padding: 8px;
  background: var(--panel);
  cursor: pointer;
}

table.preview { margin-top: 14px; font-size: 13px; }
table.preview td { vertical-align: middle; }

.import-errors {
  margin: 10px 0 0;
  padding-left: 20px;
  font-size: 13px;
  line-height: 1.6;
}

.settings-form code,
.vm-hint code {
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--notice);
  color: var(--accent-strong);
  font-size: 0.92em;
}

/* Dark theme */
:root[data-theme="dark"] .timeline-entry { border-bottom-color: #232a22; }
:root[data-theme="dark"] .contact-filters select,
:root[data-theme="dark"] .settings-form select,
:root[data-theme="dark"] .note-form select,
:root[data-theme="dark"] .settings-form input[type="file"] { background: #1c231a; }

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

@media (max-width: 860px) {
  .contact-filters { grid-template-columns: 1fr; }
  .note-form { grid-template-columns: 1fr; }
  .panel-head .head-actions { width: 100%; }
}

/* ---- Merge queue (possible duplicates) ---- */
.dupe-pair {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 16px;
  margin-bottom: 16px;
}

.dupe-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 12px;
}

.dupe-warn { margin: 0 0 12px; font-size: 13px; }

/* The two records sit side by side so the differences are read, not hunted. */
.dupe-sides {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.dupe-side {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px;
  min-width: 0;
}

.dupe-side .party-name { font-weight: 700; font-size: 15px; }

.dupe-fields {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 4px 12px;
  margin: 10px 0 0;
  font-size: 13px;
}
.dupe-fields dt { color: var(--muted); font-weight: 700; }
.dupe-fields dd { margin: 0; overflow-wrap: break-word; }

.dupe-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}
.dupe-form { display: inline; margin: 0; }

/* Phones: the timeline stacks its timestamp above the entry, and the filter
   controls hold 16px so iOS neither shrinks the selects nor zooms on focus. */
@media (max-width: 700px) {
  .dupe-sides { grid-template-columns: 1fr; }
  .dupe-actions .dupe-form,
  .dupe-actions button { width: 100%; }

  .contact-filters select,
  .note-form select,
  .settings-form select { font-size: 16px; }
  .timeline-entry { flex-direction: column; align-items: stretch; gap: 4px; }
  .entry-when { min-width: 0; }
}

/* ==================================================================
   Dashboard
   ================================================================== */

/* Four headline figures across the top, collapsing to two and then one. */
/* auto-fit rather than a fixed 4, so adding a tile fills the row instead of
   stranding one on a line of its own. */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(215px, 1fr));
  gap: 14px;
  margin-bottom: 18px;
}

/* The whole tile is the link, so the number itself is the click target. */
a.tile {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel);
  color: var(--ink);
  text-decoration: none;
  transition: border-color 0.12s ease;
}
a.tile:hover { border-color: var(--accent); }

.tile-label {
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.tile-value {
  font-size: 32px;
  font-weight: 800;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.tile-sub { color: var(--muted); font-size: 13px; }

/* The four breakdown panels. Two up on a wide screen, one on a narrow one. */
.dash-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  margin-bottom: 16px;
}
.dash-panel { min-width: 0; }

.panel-note {
  margin: -4px 0 14px;
  color: var(--muted);
  font-size: 13px;
}
.panel-head .panel-note { margin: 0; }

/* ---- Labelled bars ---- */
.bars { list-style: none; margin: 0; padding: 0; }

.bar-row {
  display: grid;
  grid-template-columns: 140px minmax(0, 1fr) 48px;
  gap: 12px;
  align-items: center;
  padding: 5px 0;
}

.bar-label {
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
a.bar-label { text-decoration: none; }
a.bar-label:hover { color: var(--accent); text-decoration: underline; }

.bar-track {
  display: block;
  height: 10px;
  border-radius: 999px;
  background: var(--notice);
  overflow: hidden;
}
.bar-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
}

.bar-count {
  text-align: right;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ---- Needs attention ---- */
.attention { list-style: none; margin: 0; padding: 0; }

.attention-row {
  padding: 10px 0;
  border-bottom: 1px solid #eef2ee;
}
.attention-row:last-child { border-bottom: 0; }
.attention-row a { color: var(--ink); text-decoration: none; }
.attention-row a:hover .attention-label { color: var(--accent); text-decoration: underline; }

.attention-count {
  font-size: 20px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.attention-label { font-weight: 700; }
.attention-why {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: 13px;
}

/* A cleared bucket is a result, not another job: mute it. */
.attention-row.clear .attention-count,
.attention-row.clear .attention-label { color: var(--muted); }

/* ---- Activity chart ---- */
.activity-total { margin: -4px 0 14px; font-size: 14px; }

/* One column per day, stacked calls over touchpoints. Fixed height so the
   columns are comparable between visits, not scaled to the panel. */
.spark {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  height: 140px;
}

.spark-col {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  height: 100%;
  min-width: 0;
}

/* Capped so a fortnight of data reads as a chart rather than as blocks; the
   column itself still divides the width evenly, keeping the day labels
   under their bars. */
.spark-stack {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  width: 100%;
  max-width: 44px;
  height: calc(100% - 20px);
}

.spark-bar { width: 100%; }
.spark-bar:first-child { border-radius: 4px 4px 0 0; }
.spark-bar:last-child { border-radius: 0 0 2px 2px; }
.spark-call { background: var(--accent); }
.spark-touch { background: #9ac9b8; }

.spark-day {
  margin-top: 6px;
  color: var(--muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.spark-legend {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 12px 0 0;
  color: var(--muted);
  font-size: 13px;
}
/* `spark-key', not `key': the dialer keypad already owns `.key', and a rule
   this far down the file would have resized every button on the console.
   flex-basis is pinned so the legend row cannot stretch the swatch into a
   bar. */
.spark-key {
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  border-radius: 3px;
}
.spark-key-call { background: var(--accent); }
.spark-key-touch { background: #9ac9b8; margin-left: 8px; }

/* ---- Segment chips (a filter arrived at from the dashboard) ---- */
.segment-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: -6px 0 14px;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--notice);
  color: var(--accent-strong);
  font-size: 13px;
  font-weight: 700;
}
.chip-clear {
  color: inherit;
  text-decoration: none;
  font-size: 15px;
  line-height: 1;
  opacity: 0.7;
}
.chip-clear:hover { opacity: 1; }

/* A pill is a chip that reports a state rather than a filter, so it is quieter:
   no accent background, because a row full of accent-coloured badges makes the
   one that matters no easier to find. `.pill` was already in the agent-changes
   markup with no rule at all, which is why it rendered as bare text there. */
.pill {
  display: inline-block;
  padding: 3px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
}
.pill.danger { color: #a23; border-color: #e0b8b1; }

/* Proposal review.
   The two answers sit side by side and wrap together on a narrow screen: a
   radio that wraps away from its label is how somebody picks the wrong one. */
.decision {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-right: 12px;
  white-space: nowrap;
  font-size: 13px;
}
/* An old value being replaced. Struck through *and* muted, because strike-out
   alone is easy to miss in a dense table and colour alone says nothing to a
   reader who cannot see it. */
.data del { text-decoration: line-through; }

/* ---- Follow-ups ----
   Prefixed `fu-` throughout. A generic name here would sit later in this flat
   stylesheet than the components it collides with and silently restyle them,
   which is exactly how a chart legend class called `.key` once resized every
   button on the dialer. */
.fu-buckets { margin-top: 0; }

.fu-chip {
  text-decoration: none;
  background: var(--panel-alt, #f3f5f3);
  color: var(--muted);
  border: 1px solid transparent;
}
.fu-chip.current {
  background: var(--notice);
  color: var(--accent-strong);
  border-color: var(--accent);
}
.fu-chip.fu-late { color: #8a1f1f; }
.fu-chip.fu-late.current { background: #fdecec; border-color: #d99; }
.fu-chip-count { font-variant-numeric: tabular-nums; opacity: 0.75; }

/* The mine/everyone pair. Both halves stay visible, so the marked one has to
   actually look marked — an unstyled `current` here leaves a control whose
   state cannot be read. */
.mini.ghost.current {
  background: var(--notice);
  border-color: var(--accent);
  color: var(--accent-strong);
}

.follow-ups td { vertical-align: top; }
.follow-ups .fu-due { white-space: nowrap; }
.fu-date { font-variant-numeric: tabular-nums; font-size: 12px; }
.fu-company { display: block; font-size: 12px; }

/* Two forms in one cell: the outcome input and Done sit on one line, Drop
   underneath, so the common action is the one under the cursor. */
.fu-actions { display: flex; flex-direction: column; gap: 6px; }
.fu-done { display: flex; gap: 6px; align-items: center; }
.fu-done .label-input { min-width: 150px; }
/* Drop is the rare action; it should not stretch to match the common one. */
.fu-actions > form:last-child { align-self: flex-start; }

/* The open follow-up, as a sentence and a caption. The note is the only part
   that says what the follow-up is for, so it is not a fragment in the middle
   of a line of metadata. */
.fu-open { margin: 0 0 14px; }
.fu-what { margin: 0; font-size: 15px; font-weight: 700; color: var(--ink); }
.fu-meta {
  margin: 5px 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  line-height: 1.4;
}
.fu-sep { opacity: 0.55; }
.fu-lead { margin: 0 0 12px; }
.fu-hint { margin: -4px 0 0; font-size: 12px; color: var(--muted); }

.fu-done-form { margin-bottom: 12px; }
.fu-set-form { margin: 12px 0 0; }
.fu-cancel { margin: 12px 0 0; }

/* "And then?" is a second question, so it is a second block. Flat siblings in
   one column is what let the date field sit there for weeks with no
   companion asking what the step actually was. */
.fu-next {
  display: flex;
  flex-direction: column;
  gap: 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px;
}
.fu-legend { margin: 0; font-size: 13px; font-weight: 700; color: var(--muted); }
/* Set a date and the block is live — the form is now going to create
   something, and says so before the button does. */
.fu-booking .fu-next { border-color: var(--accent); }

/* Shortcut offsets under a date field. A date input is precise and slow, and
   an empty one is how a follow-up ends up unbooked. */
.fu-quick { display: flex; flex-wrap: wrap; gap: 6px; margin-top: -2px; }
.fu-pick {
  min-height: 30px;
  padding: 0 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
}
.fu-pick:hover { color: var(--accent-strong); border-color: var(--accent); }
.fu-pick[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

/* Reschedule is folded away so the panel shows one primary button. */
.fu-more > summary {
  cursor: pointer;
  font-size: 13px;
  font-weight: 700;
  color: var(--muted);
  padding: 4px 0;
}
.fu-more > summary:hover { color: var(--accent-strong); }
/* Only the folded one is secondary. With no follow-up scheduled this same
   form is the panel's only action, and there it stays primary.
   `[type="submit"]` and not a bare `button`: unqualified, this outranked
   `.fu-pick[aria-pressed="true"]` and quietly bleached the pressed date
   shortcut inside the fold — the same class-collision the note at the top of
   this section is about. */
.fu-more .fu-set-form button[type="submit"] {
  background: transparent;
  color: var(--accent-strong);
  border: 1px solid var(--line);
}
.fu-more .fu-set-form button[type="submit"]:hover { background: var(--notice); }

/* The rule belongs to the heading, not the list: an unlabelled run of dated
   one-liners under "Drop this follow-up" reads as part of it. */
.fu-past {
  margin: 16px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
}

.fu-history {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  color: var(--muted);
}

/* Dark theme */
:root[data-theme="dark"] .fu-chip { background: #1c221c; }
:root[data-theme="dark"] .fu-chip.fu-late { color: #e79a9a; }
:root[data-theme="dark"] .fu-chip.fu-late.current {
  background: #2c1c1c;
  border-color: #6b3434;
}
:root[data-theme="dark"] .attention-row { border-bottom-color: #232a22; }
:root[data-theme="dark"] .spark-touch,
:root[data-theme="dark"] .spark-key-touch { background: #2b6a55; }

@media (max-width: 1100px) {
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dash-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 700px) {
  .tiles { grid-template-columns: minmax(0, 1fr); }
  .bar-row { grid-template-columns: 110px minmax(0, 1fr) 40px; gap: 8px; }
  /* Every other day, so the labels stop colliding on a phone. */
  .spark { gap: 3px; }
  .spark-col:nth-child(even) .spark-day { visibility: hidden; }
}

/* ---- Message inbox ----
   Prefixed `inbox-` for the same reason the follow-up queue is prefixed `fu-`:
   this stylesheet is flat, so a generic name added here wins over the
   components declared above it. */

/* The nav count. Lives in the top bar on every page, so it has to read at a
   glance without pulling the eye off whatever the page is actually for. */
/* An empty badge stays in the DOM as a stable out-of-band swap target but must
   read as absent, so a quiet inbox shows nothing rather than an empty pill. */
.nav-badge:empty { display: none; }

.nav-badge {
  display: inline-block;
  min-width: 18px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 11px;
  font-weight: 800;
  line-height: 16px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  vertical-align: 1px;
}

.inbox-chip {
  text-decoration: none;
  background: var(--panel-alt, #f3f5f3);
  color: var(--muted);
  border: 1px solid transparent;
}
.inbox-chip.current {
  background: var(--notice);
  color: var(--accent-strong);
  border-color: var(--accent);
}
.inbox-chip.inbox-waiting { color: var(--accent-strong); font-weight: 800; }
.inbox-chip-count { font-variant-numeric: tabular-nums; opacity: 0.75; }

.inbox {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Each message is a card rather than a table row: the body is the point, and
   a paragraph of somebody's words does not belong in a fixed-width cell. */
.inbox-item {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px 14px;
  background: var(--panel);
}

/* Cleared messages stay legible but stop competing with what is still open —
   they are only on screen at all so a mis-click can be undone. */
.inbox-item.inbox-done { opacity: 0.6; background: transparent; }

.inbox-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 6px;
}

.inbox-who {
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
}
.inbox-who:hover { text-decoration: underline; }

.inbox-company { font-size: 13px; }

/* Pushed to the far end so the timestamps line up down the list. */
.inbox-when {
  margin-left: auto;
  font-size: 13px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.inbox-body {
  margin: 0 0 10px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  line-height: 1.45;
}

.inbox-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}
.inbox-actions form { margin: 0; }

.inbox-handled { font-size: 13px; }

:root[data-theme="dark"] .inbox-chip { background: #1c221c; }
:root[data-theme="dark"] .inbox-item { border-color: #232a22; }
:root[data-theme="dark"] .inbox-item.inbox-done { background: transparent; }

@media (max-width: 700px) {
  /* The timestamp stops being an end-aligned column and just follows the
     name, so a long contact name does not squeeze it to nothing. */
  .inbox-when { margin-left: 0; width: 100%; }
  .inbox-actions { gap: 6px; }
}

/* ==================================================================
   Junk cleanup — the hidden-calls drawer and its row actions (013)
   ================================================================== */

/* One quiet line above the history saying which drawer this is. In the normal
   view it only renders when something is actually hidden, so it must not
   reserve space when absent. */
.drawer-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin-bottom: 12px;
}
.drawer-hint { font-size: 13px; }

/* The Junk / Restore / Undo buttons are one-button forms carrying hidden
   fields. Without this they inherit the form margin and push the row apart. */
.row-form { display: inline; margin: 0; }
.row-btns .row-form { display: inline-flex; }

/* The confirmation, with its Undo alongside. A div rather than a p, because a
   form inside a paragraph is closed out by the parser. */
.junk-notice {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin-bottom: 12px;
}

@media (max-width: 700px) {
  /* Three actions do not fit a phone on one line; let them wrap rather than
     overflow the row. */
  table.history .row-btns { flex-wrap: wrap; }
  td.row-actions { white-space: normal; }
}

/* ==================================================================
   Contact archive (014)
   ================================================================== */

/* Says the contact is junk, and carries the only way back. Sits above the
   fields, because arriving here from an old link should not read the same as
   arriving at a live record.

   Deliberately not .notice: that style is the green one this app uses for "that
   worked", and an archived record is a state to notice, not an outcome to feel
   good about. */
.archived-banner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin-bottom: 14px;
  padding: 12px;
  border: 1px solid #e2d3a8;
  border-left: 4px solid #c79a2e;
  border-radius: 8px;
  background: #fdf7e7;
  color: #6b4f11;
  font-size: 14px;
}
.archived-banner form { margin: 0; }

/* The archive control: last on the panel, quiet, and with the merge
   alternative named next to it rather than left to be guessed. */
.archive-action {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.archive-action form { margin: 0; }
.archive-action .muted { font-size: 13px; flex: 1 1 220px; }

:root[data-theme="dark"] .archived-banner {
  background: #2a2313;
  border-color: #4a3a1c;
  border-left-color: #c79a2e;
  color: #e8cf94;
}

/* ==================================================================
   Contact detail — the two actions that belong above the fold
   ================================================================== */

/* Call and the next step sit directly under the heading, before the edit form.
   They used to be panels below it, roughly 1600px down on a laptop. */
.contact-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}
.contact-actions form { margin: 0; display: inline-flex; }
.contact-action-hint { font-size: 13px; }

/* Calling is the primary action on this page and the only one that reaches the
   outside world, so it is the solid button; the next step is a quiet one. */
.contact-call {
  min-height: 38px;
  padding: 0 16px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 800;
}
.contact-call:hover { background: var(--accent-strong); }

/* The live call-state target: present for the socket, invisible until it has
   something in it. */
.contact-actions + .call-state { margin: 0 0 16px; }
.call-state:empty { display: none; }

/* The due badge rides inside the button, so the answer to "what do I owe this
   vendor" is readable without opening the modal. */
.contact-next { gap: 8px; }
.contact-next .badge { font-weight: 700; }

/* Wider than the default modal: it holds two stacked forms and the history.
   And capped, because with the fold open on a phone it is taller than the
   viewport — better to scroll the modal than to lose its bottom edge. */
dialog.next-step-modal {
  width: min(560px, 92vw);
  max-height: 88vh;
  overflow-y: auto;
}
/* The modal's title and the follow-up's own line are both bold and were
   touching, which read as one two-line heading. */
.next-step-modal .modal-head { margin-bottom: 14px; }
.next-step-modal .fu-more { margin-top: 14px; }

/* ---- Connectors (OAuth clients) ---- */

/* Registering a connector is a rare, deliberate act, so the form is folded
   away and the panel leads with the list of what is already registered. */
.add-form { margin-top: 14px; }
.add-form > summary {
  cursor: pointer;
  font-size: 13px;
  font-weight: 700;
  color: var(--muted);
  padding: 4px 0;
}
.add-form > summary:hover { color: var(--accent-strong); }
/* Breathing room between the fold's label and the first field, which otherwise
   sit close enough to read as one control. */
.add-form > .settings-form { margin-top: 10px; }
/* The secret is shown exactly once and has to be selected accurately, so it
   gets its own line and wraps rather than scrolling out of reach. */
.secret-banner code {
  display: inline-block;
  word-break: break-all;
  user-select: all;
}

/* Widening a connector's authority is folded away so it is something you go
   looking for, not something a mis-click while reading the table can do. */
.scope-edit { margin-top: 6px; }
.scope-edit > summary {
  cursor: pointer;
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
}
.scope-edit > summary:hover { color: var(--accent-strong); }
/* `align-items` has to be stated, not just `display` and `direction`. The base
   `form` rule sets `align-items: end`, which is right for the row of inputs it
   was written for and means "push to the right edge" once the direction is
   column — so the checkboxes came out right-aligned against a ragged left edge.
   `.settings-form` overrides it too, which is why the registration form below
   this one looked correct while this one did not. */
.scope-edit form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  margin-top: 8px;
}

/* A checkbox and its wording on one line.
   Two base rules have to be undone to get there, and both are right for the
   text inputs they were written for: `label` is `display: grid`, which stacks
   the box above its own label, and `input` is `width: 100%; min-height: 42px`,
   which turns a checkbox into a large empty square. Modelled on
   `label.role-option`, which solved the same problem for the role picker. */
.check {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  color: var(--ink);
  font-size: 13px;
  font-weight: 400;
}
.check input[type="checkbox"] {
  width: auto;
  min-height: 0;
  flex: none;
  margin: 0;
  padding: 0;
}
/* The maximum-scopes fieldset on the registration form holds the same labels
   and inherits the same three-column form grid, so it needs the same escape. */
.settings-form fieldset {
  display: flex;
  flex-direction: column;
  gap: 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 10px 12px;
  margin: 0;
}
.settings-form legend {
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
  padding: 0 4px;
}
