/* ============================================================================
   Zetigon Connect v2 — one stylesheet, both apps.

   The staff area and the client workspace are different products with different
   data, but they are the same shape: a fixed rail on the left, everything else on
   the right. Sharing this file is what stops them drifting into two house styles,
   which is what happened to v1 — the tokens were copied between two 3,000-line
   documents and then edited in one of them.

   Colours are carried over from v1 unchanged. The palette was already right; it
   was the layout around it that was not.
   ========================================================================== */

:root {
  /* -- brand ------------------------------------------------------------- */
  --brand: #172552;                /* primary navy: the rail, headings */
  --brand-soft: #24356b;           /* one step up, for hover on the rail */
  --brand-ink: #ffffff;

  /* --accent is the secondary brand blue (#29a9e1) darkened to #1a7ba8 wherever
     it is READ. The brand value is 2.67:1 on white, which fails AA for body text
     (4.5:1) and even the 3:1 for interface edges. It is kept exactly as given for
     fills, and darkened only where somebody has to read it. */
  --accent: #1a7ba8;
  --accent-fill: #29a9e1;
  --accent-soft: #e8f5fb;

  /* -- surfaces ---------------------------------------------------------- */
  --bg: #f7f8f2;
  --panel: #ffffff;
  --line: #e4e6de;
  --line-soft: #eef0ea;
  --ink: #1f2430;
  --muted: #5c6472;
  --faint: #8b93a3;

  /* -- state ------------------------------------------------------------- */
  --warn: #8a5a12;    --warn-soft: #fdf4e6;
  --danger: #b8243f;  --danger-soft: #fbecef;
  --good: #1d7a4c;    --good-soft: #e7f5ed;

  /* -- geometry ---------------------------------------------------------- */
  --rail: 244px;
  --r-sm: 6px;
  --r: 10px;
  --r-lg: 14px;
  --shadow: 0 1px 2px rgba(23, 37, 82, .06), 0 4px 12px rgba(23, 37, 82, .05);
  --shadow-lg: 0 8px 32px rgba(23, 37, 82, .16);
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 400 14px/1.5 Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -.011em; color: var(--brand); }
h1 { font-size: 21px; }
h2 { font-size: 16px; }
h3 { font-size: 14px; }
p  { margin: 0; }
a  { color: var(--accent); }

/* Numbers line up in columns only if they are tabular. Every figure in a table,
   every stat, every cap. */
.num { font-variant-numeric: tabular-nums; }

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

.shell { display: flex; min-height: 100vh; }

/* -- the rail --------------------------------------------------------------
   Fixed rather than sticky: the content column scrolls, the navigation does not
   move at all. */
.rail {
  width: var(--rail);
  flex: 0 0 var(--rail);
  background: var(--brand);
  color: var(--brand-ink);
  display: flex;
  flex-direction: column;
  position: fixed;
  inset: 0 auto 0 0;
  z-index: 40;
}

.rail-brand {
  display: flex; align-items: center; gap: 10px;
  padding: 20px 20px 18px;
  font-size: 15px; font-weight: 600; color: #fff;
  letter-spacing: -.01em;
}
/* Sized by height, not width: the source is 637x140 and scaling by width would make the
   logo's size depend on the rail's, which changes at the breakpoint. */
.rail-logo { height: 26px; width: auto; display: block; }
/* Hidden until the rail collapses — see the breakpoint. */
.rail-brand .rail-mark { display: none; }
.rail-mark {
  width: 28px; height: 28px; flex: 0 0 28px;
  border-radius: 8px;
  background: var(--accent-fill);
  display: grid; place-items: center;
  font-weight: 700; font-size: 14px; color: #06263a;
}
.rail-sub {
  padding: 0 20px 16px;
  font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: rgba(255, 255, 255, .42);
}

.rail-nav { flex: 1; overflow-y: auto; padding: 4px 12px; }

/* A heading over a group of nav items. Quiet enough to scan past, present enough that
   "am I configuring the platform or working on a customer?" has an answer. */
.rail-section {
  padding: 16px 12px 6px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: rgba(255, 255, 255, .34);
}
.rail-nav > .rail-section:first-child { padding-top: 4px; }

/* A count on a nav item — a review queue that is empty should not shout. */
.rail-badge {
  margin-left: auto;
  background: var(--accent-fill); color: #06263a;
  font-size: 11px; font-weight: 700;
  border-radius: 10px; padding: 1px 7px;
}

.rail-item {
  display: flex; align-items: center; gap: 11px;
  width: 100%;
  padding: 9px 12px;
  margin-bottom: 2px;
  border: 0; border-radius: var(--r-sm);
  background: transparent;
  color: rgba(255, 255, 255, .74);
  font: inherit; font-weight: 500; text-align: left;
  cursor: pointer;
  transition: background .12s, color .12s;
}
.rail-item:hover { background: var(--brand-soft); color: #fff; }
.rail-item.is-active { background: var(--brand-soft); color: #fff; }

/* The active marker is a bar in the brand blue at full strength — this is a fill,
   not text, so it does not need the darkened value. */
.rail-item.is-active { position: relative; }
.rail-item.is-active::before {
  content: ""; position: absolute; left: -12px; top: 6px; bottom: 6px;
  width: 3px; border-radius: 0 3px 3px 0; background: var(--accent-fill);
}
.rail-item svg { width: 17px; height: 17px; flex: 0 0 17px; opacity: .9; }

.rail-foot { padding: 12px; border-top: 1px solid rgba(255, 255, 255, .1); }
.rail-who { padding: 4px 12px 10px; }
.rail-who-name { font-size: 13px; font-weight: 600; color: #fff; }
.rail-who-sub  {
  font-size: 12px; color: rgba(255, 255, 255, .5);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* -- the content column ---------------------------------------------------- */
.main { flex: 1; min-width: 0; margin-left: var(--rail); display: flex; flex-direction: column; }

.topbar {
  display: flex; align-items: center; gap: 16px;
  padding: 18px 28px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 30;
}
.topbar-title { flex: 1; min-width: 0; }
.topbar-title p { color: var(--muted); font-size: 13px; margin-top: 2px; }
.topbar-actions { display: flex; gap: 8px; flex: 0 0 auto; }

.content { padding: 24px 28px 48px; flex: 1; }
.content-narrow { max-width: 760px; }

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

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 8px 14px;
  border: 1px solid transparent; border-radius: var(--r-sm);
  font: inherit; font-weight: 500; font-size: 13px;
  cursor: pointer; white-space: nowrap;
  transition: background .12s, border-color .12s, opacity .12s;
}
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn svg { width: 15px; height: 15px; }

.btn-primary { background: var(--brand); color: #fff; }
.btn-primary:not(:disabled):hover { background: var(--brand-soft); }

.btn-ghost { background: var(--panel); color: var(--ink); border-color: var(--line); }
.btn-ghost:not(:disabled):hover { background: var(--bg); border-color: #d3d7cc; }

.btn-quiet { background: transparent; color: var(--muted); padding: 6px 9px; }
.btn-quiet:not(:disabled):hover { background: var(--bg); color: var(--ink); }

.btn-danger { background: var(--danger); color: #fff; }
.btn-danger:not(:disabled):hover { background: #a01f37; }

.btn-sm { padding: 5px 10px; font-size: 12.5px; }
.btn-block { width: 100%; }

/* -- fields ---------------------------------------------------------------- */
.field { margin-bottom: 20px; }
.field-row { display: flex; gap: 16px; }
.field-row > .field { flex: 1; margin-bottom: 20px; }

.label {
  display: block; margin-bottom: 7px;
  font-size: 12.5px; font-weight: 500; color: var(--ink);
}
.hint { margin-top: 7px; font-size: 12px; color: var(--muted); line-height: 1.5; }

.input, .select, .textarea {
  width: 100%; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--panel); color: var(--ink);
  font: inherit; font-size: 13.5px;
  transition: border-color .12s, box-shadow .12s;
}
.input:focus, .select:focus, .textarea:focus {
  outline: 0; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.input.is-bad, .select.is-bad { border-color: var(--danger); }
.input::placeholder { color: var(--faint); }
.textarea { resize: vertical; min-height: 76px; }
.select { appearance: none; padding-right: 30px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath fill='%235c6472' d='M6 8.5 2 4.5h8z'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center; background-size: 11px; }

.err-text { margin-top: 7px; font-size: 12px; color: var(--danger); }

/* A checkbox that reads as a row, because these sit in forms next to caps. */
.check { display: flex; align-items: flex-start; gap: 11px; cursor: pointer; padding: 3px 0; }
.check input { margin: 2px 0 0; width: 15px; height: 15px; accent-color: var(--accent); flex: 0 0 15px; }
.check-text { font-size: 13px; }
.check-text span { display: block; color: var(--muted); font-size: 12px; margin-top: 3px; line-height: 1.45; }

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

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow);
}
.card + .card { margin-top: 18px; }
.card-head {
  display: flex; align-items: center; gap: 12px;
  padding: 16px 20px; border-bottom: 1px solid var(--line-soft);
}
.card-head h2 { flex: 1; min-width: 0; }
.card-head p { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.card-body { padding: 20px; }

/* -- stats ----------------------------------------------------------------- */
/* 190px fitted five cards and wrapped the sixth onto a row of its own, which reads as a
   card that failed to load rather than as a grid. Six is the real maximum here — a total
   plus the four statuses, plus one more on the index — so the minimum is set to whatever
   lets six sit together on a normal window and still wrap sensibly below that. */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); gap: 14px; }
.stat {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); padding: 16px 18px; box-shadow: var(--shadow);
}
.stat-label { font-size: 12px; font-weight: 500; color: var(--muted); }
.stat-value { font-size: 27px; font-weight: 600; color: var(--brand); margin-top: 6px; letter-spacing: -.02em; }
.stat-foot { font-size: 12px; color: var(--faint); margin-top: 4px; }

/* -- tables ---------------------------------------------------------------- */
/* -- a table inside a card ------------------------------------------------- */
/*
 * The card is rounded and does NOT clip. That is deliberate: overflow:hidden here would
 * also clip the date calendar and the country dropdown, which open inside cards on the
 * segment builder — the same fault that cut the signup form's dropdown in half.
 *
 * So the table rounds its own corners instead. Without it the square corners of a header
 * or a last row sit outside the card's curve: invisible while everything is white, and
 * suddenly visible the moment tr:hover paints a background. That is the "sometimes round,
 * sometimes square, and it changes when you hover" — it was always square, and hovering
 * was what made it show.
 */
.table-wrap { overflow-x: auto; }

.card > .table-wrap:first-child .tbl thead tr:first-child th:first-child {
  border-top-left-radius: calc(var(--r) - 1px);
}
.card > .table-wrap:first-child .tbl thead tr:first-child th:last-child {
  border-top-right-radius: calc(var(--r) - 1px);
}
.card > .table-wrap:last-child .tbl tbody tr:last-child td:first-child {
  border-bottom-left-radius: calc(var(--r) - 1px);
}
.card > .table-wrap:last-child .tbl tbody tr:last-child td:last-child {
  border-bottom-right-radius: calc(var(--r) - 1px);
}
/* The last row's rule would otherwise cut straight across the corner it just rounded. */
.card > .table-wrap:last-child .tbl tbody tr:last-child td { border-bottom: 0; }
table.tbl { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.tbl th {
  text-align: left; padding: 12px 20px 10px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); background: var(--panel);
  border-bottom: 1px solid var(--line); white-space: nowrap;
}
.tbl td { padding: 13px 20px; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
.tbl tr:last-child td { border-bottom: 0; }
.tbl tbody tr:hover { background: #fbfcf8; }
.tbl .right { text-align: right; }
.tbl-name { font-weight: 500; color: var(--ink); }
.tbl-sub { font-size: 12px; color: var(--muted); margin-top: 1px; }
.tbl-actions { display: flex; gap: 6px; justify-content: flex-end; }

/* -- badges ---------------------------------------------------------------- */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 8px; border-radius: 20px;
  font-size: 11.5px; font-weight: 500; white-space: nowrap;
}
.badge-good   { background: var(--good-soft);   color: var(--good); }
.badge-warn   { background: var(--warn-soft);   color: var(--warn); }
.badge-danger { background: var(--danger-soft); color: var(--danger); }
.badge-accent { background: var(--accent-soft); color: var(--accent); }
.badge-plain  { background: var(--bg); color: var(--muted); border: 1px solid var(--line); }

.chips { display: flex; flex-wrap: wrap; gap: 5px; }

/* A meter for "how much of the cap is used". No bar is drawn for an unlimited
   cap, because a percentage of unlimited is not a number. */
.meter { height: 5px; border-radius: 3px; background: var(--line-soft); overflow: hidden; min-width: 70px; }
.meter > i { display: block; height: 100%; border-radius: 3px; background: var(--accent-fill); }
.meter.is-warn > i   { background: #d9a441; }
.meter.is-danger > i { background: var(--danger); }

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

.overlay {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(23, 37, 82, .42);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 6vh 20px 20px; overflow-y: auto;
}
.dialog {
  width: 100%; max-width: 520px;
  background: var(--panel); border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
}
.dialog-head { display: flex; align-items: center; gap: 12px; padding: 22px 24px 0; }
.dialog-head h2 { flex: 1; }
.dialog-body { padding: 20px 24px; }
.dialog-foot {
  display: flex; justify-content: flex-end; gap: 10px;
  padding: 16px 24px; border-top: 1px solid var(--line-soft);
}

/* ================================================================= misc === */

.empty { padding: 44px 20px; text-align: center; }
.empty h3 { color: var(--ink); margin-bottom: 5px; }
.empty p { color: var(--muted); font-size: 13px; max-width: 380px; margin: 0 auto 16px; }

.spinner {
  width: 16px; height: 16px; border-radius: 50%;
  /* A short arc rather than a ring with one coloured quarter: at 16px the old version read
     as a static circle, because three of its four sides never changed. The arc is mostly
     transparent, so what the eye follows is a moving gap. */
  border: 2px solid transparent;
  border-top-color: currentColor;
  border-right-color: currentColor;
  animation: spin .55s cubic-bezier(.6, .1, .4, .9) infinite;
  display: inline-block;
}
/* Eased, not linear. A constant-speed rotation at this size looks mechanical; a slight
   acceleration through each turn reads as effort. */
@keyframes spin { to { transform: rotate(360deg); } }

/* The full-screen loader gets a second, slower ring going the other way — enough motion to
   say "still working" during the several seconds a provider call can take. */
.loading .spinner { width: 22px; height: 22px; border-width: 2.5px; color: var(--accent); position: relative; }
.loading .spinner::after {
  content: ""; position: absolute; inset: 3px; border-radius: 50%;
  border: 2px solid transparent; border-bottom-color: var(--accent-fill); opacity: .55;
  animation: spin 1.1s linear infinite reverse;
}
.loading { display: flex; align-items: center; justify-content: center; gap: 9px; padding: 44px; color: var(--muted); font-size: 13px; }

.alert { padding: 12px 15px; border-radius: var(--r-sm); font-size: 13px; line-height: 1.5; margin-bottom: 18px; }
.alert-danger { background: var(--danger-soft); color: var(--danger); }
.alert-good   { background: var(--good-soft);   color: var(--good); }
.alert-warn   { background: var(--warn-soft);   color: var(--warn); }

/* Toasts confirm the thing that just happened without taking the screen. */
.toasts { position: fixed; right: 20px; bottom: 20px; z-index: 200; display: flex; flex-direction: column; gap: 8px; }
.toast {
  background: var(--brand); color: #fff;
  padding: 11px 16px; border-radius: var(--r-sm);
  font-size: 13px; box-shadow: var(--shadow-lg);
  animation: rise .18s ease-out;
}
.toast.is-bad { background: var(--danger); }
@keyframes rise { from { opacity: 0; transform: translateY(6px); } }

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

.auth { min-height: 100vh; display: grid; place-items: center; padding: 24px; background: var(--bg); }
.auth-box { width: 100%; max-width: 396px; }
.auth-brand { display: flex; align-items: center; gap: 11px; justify-content: center; margin-bottom: 22px; }
.auth-brand .rail-mark { width: 34px; height: 34px; flex-basis: 34px; border-radius: 9px; font-size: 16px; }
.auth-brand-name { font-size: 17px; font-weight: 600; color: var(--brand); }
.auth-card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 26px; box-shadow: var(--shadow); }
.auth-card h1 { font-size: 18px; margin-bottom: 5px; }
.auth-card > p { color: var(--muted); font-size: 13px; margin-bottom: 20px; }
.auth-foot { margin-top: 16px; text-align: center; font-size: 13px; }
.auth-foot button {
  background: 0; border: 0; padding: 0; font: inherit;
  color: var(--accent); cursor: pointer; text-decoration: underline;
}
.auth-note { margin-top: 18px; text-align: center; font-size: 12px; color: var(--faint); }

/* ================================================================= detail = */
/* Two columns where there is room, one where there is not. The right-hand column
   carries what changes a client's behaviour — package, ceilings, suspension — so it
   stays beside the identity rather than below a long list of people. */
.cols { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: start; }
.cols > div > .card:first-child { margin-top: 0; }
@media (max-width: 1100px) { .cols { grid-template-columns: 1fr; } }

/* A divider inside a dialog: what is being shown, above what is being changed. */
hr.rule { border: 0; border-top: 1px solid var(--line-soft); margin: 18px 0; }

/* A permission or scope name. Monospace because it is a literal to be matched against a
   checkbox in somebody else's console, not prose. */
code.perm {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12.5px; font-weight: 600;
  background: rgba(0, 0, 0, .06); border-radius: 4px; padding: 1px 5px;
}

/* A DNS host or value: a literal to be copied into somebody's zone file, character for
   character, so it is set in monospace and allowed to wrap rather than being truncated. */
.dns-cell {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; word-break: break-all;
}

/* The setup checklist. Ordered, because the order is real: a number cannot be assigned
   before the account it lives on is chosen. */
.checklist-row {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 11px 0; border-bottom: 1px solid var(--line-soft);
}
.checklist-row:last-child { border-bottom: 0; padding-bottom: 0; }
.checklist-row:first-child { padding-top: 0; }
.checklist-mark {
  width: 20px; height: 20px; flex: 0 0 20px; margin-top: 1px;
  border: 1.5px solid var(--line); border-radius: 50%;
  display: grid; place-items: center;
}
.checklist-row.is-done .checklist-mark {
  background: var(--good); border-color: var(--good); color: #fff;
}
.checklist-mark svg { width: 13px; height: 13px; }
.checklist-text { flex: 1; min-width: 0; }
.checklist-label { font-size: 13.5px; font-weight: 500; }
.checklist-row.is-done .checklist-label { color: var(--muted); }
.checklist-why { font-size: 12px; color: var(--muted); margin-top: 2px; }

/* The client you are setting up, carried onto another screen. */
.ctx-bar {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; margin-bottom: 18px;
  background: var(--accent-soft); border: 1px solid #cfe8f5; border-radius: var(--r-sm);
  font-size: 13px;
}
.ctx-bar span { flex: 1; }

/* The message as a recipient will see it, with the client's own examples substituted.
   Set apart from the interface because it is content, not chrome. */
.rev-preview {
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 12px 14px; margin-top: 4px;
  font-size: 13.5px; line-height: 1.55; white-space: pre-wrap;
}

/* A displayed value. Deliberately not styled like an input: if it looks like a field,
   somebody will try to type in it and wonder why nothing happens. */
.shown + .shown { margin-top: 18px; }
.shown-label { font-size: 12.5px; font-weight: 500; color: var(--muted); margin-bottom: 5px; }
.shown-value { font-size: 14px; color: var(--ink); }
.shown-value .num { font-variant-numeric: tabular-nums; }
.shown-sub { font-size: 12px; color: var(--faint); margin-top: 2px; }
.shown-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 18px 20px; }
.shown-cols .shown + .shown { margin-top: 0; }

/* Filter tabs in a card header. Segmented rather than a dropdown: there are four, they
   are all worth seeing, and each carries a count that is itself the answer to a question. */
.tabs { display: flex; gap: 2px; background: var(--bg); padding: 2px; border-radius: var(--r-sm); }
.tab {
  border: 0; background: transparent; border-radius: 4px;
  padding: 5px 10px; font: inherit; font-size: 12.5px; font-weight: 500;
  color: var(--muted); cursor: pointer; white-space: nowrap;
}
.tab:hover { color: var(--ink); }
.tab.is-on { background: var(--panel); color: var(--ink); box-shadow: var(--shadow); }
.tab .num { color: var(--faint); margin-left: 3px; }
.tab.is-on .num { color: var(--accent); }

/* A row that opens something. The pointer is the real affordance; the hint appears on
   hover so the column is not a permanent arrow doing nothing. It is aria-hidden and the
   name in the first cell stays a focusable button, so the keyboard is not relying on it. */
.tbl tr.row-open { cursor: pointer; }
.row-open-hint {
  font-size: 12.5px; color: var(--accent); font-weight: 500;
  opacity: 0; transition: opacity .12s;
}
.tbl tr.row-open:hover .row-open-hint,
.tbl tr.row-open:focus-within .row-open-hint { opacity: 1; }

/* A table cell that is a link but must not look like a button. */
.linkish {
  background: 0; border: 0; padding: 0; font: inherit; font-weight: 500;
  color: var(--accent); cursor: pointer; text-align: left;
}
.linkish:hover { text-decoration: underline; }

/* ========================================================== impersonation = */
/* Loud on purpose. Someone acting as a client must never forget they are — so the bar
   is FIXED, and the shell is pushed down to make room rather than sliding under it. */
.imp-bar {
  position: fixed; inset: 0 0 auto 0; z-index: 60;
  display: flex; align-items: center; gap: 12px;
  height: var(--imp-h);
  padding: 0 20px;
  background: #7a2231; color: #fff;
  font-size: 13px; font-weight: 500;
  box-shadow: 0 1px 0 rgba(0, 0, 0, .18);
}
.imp-said { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.imp-said strong { font-weight: 700; }
.imp-more { opacity: .78; font-weight: 400; }
.imp-bar .btn { background: rgba(255, 255, 255, .16); color: #fff; border-color: transparent; }
.imp-bar .btn:not(:disabled):hover { background: rgba(255, 255, 255, .26); }

/* A slow pulse: motion is what the eye catches when the text has stopped being read. */
.imp-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px;
  background: #ff8fa1; animation: impulse 2.4s ease-in-out infinite;
}
@keyframes impulse { 50% { opacity: .35; } }

/* The same way out, without scrolling back to the top. */
.imp-float {
  position: fixed; right: 20px; bottom: 20px; z-index: 60;
  display: flex; align-items: center; gap: 8px;
  padding: 10px 16px; border: 0; border-radius: 24px;
  background: #7a2231; color: #fff;
  font: inherit; font-size: 13px; font-weight: 600;
  box-shadow: 0 6px 20px rgba(122, 34, 49, .38);
  cursor: pointer;
}
.imp-float:hover { background: #8d2839; }
.imp-float:disabled { opacity: .6; cursor: progress; }
.imp-float svg { width: 16px; height: 16px; }

/* While impersonating, everything moves down by the height of the bar. Declared on the
   body so the rail — which is fixed, and therefore ignores any wrapper's padding — moves
   with the rest of the page instead of sitting underneath the warning. */
:root { --imp-h: 0px; }
body.is-impersonating { --imp-h: 42px; padding-top: var(--imp-h); }
body.is-impersonating .rail { top: var(--imp-h); }
body.is-impersonating .topbar { top: var(--imp-h); }

@media (max-width: 700px) {
  .imp-more { display: none; }
  .imp-float span { display: none; }
  .imp-float { padding: 12px; border-radius: 50%; }
}

/* ============================================================== narrow ==== */
/* The rail collapses to icons rather than disappearing behind a hamburger: the
   navigation is four items, and hiding four items behind a button is worse. */
@media (max-width: 860px) {
  :root { --rail: 60px; }
  .rail-brand { padding: 18px 0; justify-content: center; }
  .rail-brand span, .rail-sub, .rail-who, .rail-item span { display: none; }
  /* The wordmark cannot be read at 60px wide, so the lettered tile takes its place. */
  .rail-logo { display: none; }
  .rail-brand .rail-mark { display: grid; }
  /* A heading with nothing legible under it is just a gap. */
  .rail-section { display: none; }
  .rail-badge { position: absolute; top: 4px; right: 4px; margin: 0; padding: 0 5px; }
  .rail-item { justify-content: center; padding: 11px 0; }
  .rail-item.is-active::before { left: -12px; }
  .rail-nav { padding: 4px 12px; }
  .topbar, .content { padding-left: 16px; padding-right: 16px; }
  .imp-bar { padding-left: 16px; padding-right: 16px; }
}

/* -- a settings page: its own nav on the left, the panel on the right ------- */
/* Tabs across the top stopped scaling at three; Settings now holds five sections and
   will hold more. A vertical list also repeats the shape of the main rail, so "where
   am I" is answered the same way twice rather than two different ways. */
.settings { display: grid; grid-template-columns: 208px minmax(0, 1fr); gap: 24px; }

.subnav { position: sticky; top: 18px; align-self: start; }
.subnav-section {
  padding: 14px 10px 6px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--faint);
}
.subnav-section:first-child { padding-top: 0; }

.subnav-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 8px 10px; margin-bottom: 2px;
  border: 0; border-radius: var(--r-sm);
  background: transparent; color: var(--muted);
  font: inherit; font-weight: 500; text-align: left;
  cursor: pointer; transition: background .12s, color .12s;
}
.subnav-item:hover { background: var(--bg); color: var(--ink); }
.subnav-item.is-on { background: var(--panel); color: var(--ink); box-shadow: var(--shadow); }
.subnav-item svg { width: 16px; height: 16px; flex: 0 0 16px; opacity: .75; }
.subnav-item.is-on svg { opacity: 1; color: var(--accent); }

/* A section that needs attention says so here, so it is visible without opening it. */
.subnav-dot {
  margin-left: auto; width: 7px; height: 7px; border-radius: 50%;
  background: var(--warn-fill);
}

/* Below the rail's own breakpoint the two columns become one — a 208px nav beside a
   panel on a phone leaves neither enough room to be read. */
@media (max-width: 900px) {
  .settings { grid-template-columns: 1fr; gap: 14px; }
  .subnav { position: static; display: flex; flex-wrap: wrap; gap: 4px; }
  .subnav-section { display: none; }
  .subnav-item { width: auto; }
}

/* -- an address, shown as one block rather than six labelled lines ---------- */
.address { font-size: 13.5px; line-height: 1.55; color: var(--ink); white-space: pre-line; }

/* -- a chip: a small named thing that can be removed ----------------------- */
/* Used for a list's own columns. A table would give six rows of chrome to what is really
   a short list of labels, and a bare comma-joined string gives nothing to click. */
.chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 6px 5px 11px;
  background: var(--bg); border: 1px solid var(--line); border-radius: 999px;
  font-size: 12.5px; color: var(--ink);
}
.chip-sub { color: var(--faint); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
.chip-x {
  border: 0; background: transparent; cursor: pointer;
  color: var(--muted); font-size: 15px; line-height: 1;
  padding: 0 5px; border-radius: 999px;
}
.chip-x:hover { background: var(--danger-soft); color: var(--danger); }

/* The same shape, but the whole chip is the button. */
.chip-link { cursor: pointer; font: inherit; font-size: 12.5px; padding: 5px 11px; }
.chip-link:hover { border-color: var(--accent); color: var(--accent); }

/* -- pagination ------------------------------------------------------------ */
.pager {
  display: flex; align-items: center; justify-content: center; gap: 16px;
  padding: 14px 20px; border-top: 1px solid var(--line);
}

/* -- a phone number, in two parts ------------------------------------------ */
/* Country and national number side by side, because that is how people know their own
   number. The + prefix is shown inside the field but is not part of the text, so it
   cannot be deleted by accident. */
.phone-input { display: flex; gap: 10px; }
.phone-number { position: relative; flex: 1; min-width: 0; }
.phone-dial {
  position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  font-size: 13.5px; color: var(--muted); pointer-events: none;
}
.phone-number .input.has-dial { padding-left: 46px; }

@media (max-width: 560px) {
  .phone-input { flex-direction: column; }
}

/* -- a date, with a calendar ----------------------------------------------- */
.date-input { position: relative; }
.date-face { position: relative; }
.date-face .input { padding-right: 38px; }
.date-open {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  border: 0; background: transparent; cursor: pointer; padding: 6px;
  color: var(--muted); border-radius: var(--r-sm); line-height: 0;
}
.date-open:hover { background: var(--bg); color: var(--accent); }
.date-open svg { width: 17px; height: 17px; }
.date-said { margin-top: 5px; font-size: 12.5px; color: var(--muted); }

.cal {
  position: absolute; z-index: 60; top: calc(100% + 6px); left: 0;
  width: 268px; padding: 10px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-sm); box-shadow: 0 8px 28px rgba(15, 23, 42, .16);
}
.cal-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 8px; font-size: 13.5px; font-weight: 600;
}
.cal-head button {
  border: 0; background: transparent; cursor: pointer; color: var(--muted);
  font-size: 19px; line-height: 1; padding: 2px 9px; border-radius: var(--r-sm);
}
.cal-head button:hover { background: var(--bg); color: var(--ink); }

.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.cal-dow {
  text-align: center; font-size: 10.5px; font-weight: 700; color: var(--faint);
  padding-bottom: 4px; text-transform: uppercase;
}
.cal-day {
  border: 0; background: transparent; cursor: pointer;
  padding: 7px 0; border-radius: var(--r-sm);
  font: inherit; font-size: 12.5px; color: var(--ink);
}
.cal-day:hover { background: var(--bg); }
/* Today is outlined, the selection is filled — so "where am I" and "what did I pick" stay
   legible when they are the same day. */
.cal-day.is-today { box-shadow: inset 0 0 0 1px var(--line); font-weight: 600; }
.cal-day.is-on { background: var(--accent); color: #fff; font-weight: 600; }

.cal-foot {
  display: flex; justify-content: space-between; gap: 8px;
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line);
}
.cal-foot button {
  border: 0; background: transparent; cursor: pointer;
  font: inherit; font-size: 12.5px; color: var(--accent);
  padding: 4px 8px; border-radius: var(--r-sm);
}
.cal-foot button:hover { background: var(--bg); }

/* -- a search box that fills its row --------------------------------------- */
/* The old one inherited an input's default width and sat as a stub in a wide card. */
.searchbar { display: flex; gap: 12px; align-items: stretch; padding: 16px 20px 6px; }
/* Everything in this row is one height. The buttons were sizing to their own text while
   the inputs sized to their padding, so a Filter button sat a few pixels short of the box
   beside it. */
.searchbar > .btn, .searchbar > .input, .searchbar > .select { height: 40px; }
.searchbar > .btn { padding: 0 14px; display: inline-flex; align-items: center; gap: 7px; }
.searchbar .search-field .input { height: 40px; }
.searchbar .search-field { position: relative; flex: 1 1 auto; min-width: 0; }
.searchbar .search-field .input { width: 100%; padding-left: 36px; }
.searchbar .search-icon {
  position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; color: var(--faint); pointer-events: none;
}
.searchbar .select { flex: 0 0 auto; width: auto; min-width: 150px; }

/* -- a wizard's step rail --------------------------------------------------- */
.steps { display: flex; gap: 8px; margin-bottom: 24px; }
.step {
  flex: 1; padding: 10px 12px; border-radius: var(--r-sm);
  background: var(--bg); font-size: 12.5px; color: var(--muted);
  border-top: 2px solid var(--line);
}
.step.is-on { color: var(--ink); font-weight: 600; border-top-color: var(--accent); background: var(--panel); }
.step.is-done { color: var(--good); border-top-color: var(--good-fill, var(--good)); }
.step-n { font-weight: 700; margin-right: 6px; opacity: .6; }

/* -- a group of radio choices ---------------------------------------------- */
/* Stacked, not inline: choices are often sentences, and a wrapped inline row puts the
   circle of one option next to the text of another. */
.radios { display: flex; flex-direction: column; gap: 2px; }
.radios .check { margin-bottom: 0; }

/* A textarea borrowing .input needs the two things an input gets for free. */
textarea.input { resize: vertical; min-height: 64px; font: inherit; line-height: 1.5; }

/* A status badge that is also a button — it opens the dialog that changes it. */
.tbl .linkish { padding: 0; border: 0; background: none; cursor: pointer; }

/* -- a country field you can type into ------------------------------------- */
.cpick { position: relative; flex: 0 0 42%; min-width: 0; }
.cpick-field { width: 100%; padding-right: 26px; text-overflow: ellipsis; }
.cpick-caret {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  color: var(--faint); font-size: 11px; pointer-events: none;
}

.cpick-menu {
  position: absolute; z-index: 70; top: calc(100% + 4px); left: 0;
  /* Wider than the field, which is 42% of the phone row — otherwise the longer country
     names are clipped with nothing gained. */
  min-width: 100%; width: max-content; max-width: min(340px, 90vw);
  max-height: 300px; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-sm); box-shadow: 0 8px 28px rgba(15, 23, 42, .16);
  padding: 6px;
}
.cpick-row {
  display: flex; align-items: baseline; gap: 12px; width: 100%;
  padding: 9px 11px; border: 0; background: transparent; border-radius: var(--r-sm);
  font: inherit; font-size: 13px; text-align: left; cursor: pointer; color: var(--ink);
}
/* Hover and keyboard share one highlight, so arrowing and pointing cannot disagree about
   which row is about to be chosen. */
.cpick-row.is-active { background: var(--bg); }
.cpick-row.is-on { color: var(--accent); font-weight: 600; }
.cpick-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cpick-dial { color: var(--faint); font-size: 12.5px; }
.cpick-row.is-on .cpick-dial { color: var(--accent); }
.cpick-none { padding: 16px 12px; font-size: 13px; color: var(--muted); }

@media (max-width: 560px) {
  .cpick { flex: 1 1 auto; }
}

/* The line between the pinned countries and everywhere else. A caption rather than a bare
   rule, because a rule alone reads as a rendering glitch when the rows above it are the
   only ones out of alphabetical order. */
.cpick-split {
  margin: 6px 4px 3px; padding: 6px 7px 4px;
  border-top: 1px solid var(--line);
  font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--faint);
}
.cpick-menu > .cpick-split:first-child { border-top: 0; margin-top: 0; }
/* Flipped above the field when there is no room below it — a phone field low in a dialog
   would otherwise open its list past the bottom of the window. */
.cpick-menu.is-up { top: auto; bottom: calc(100% + 4px); }

/* -- the file library ------------------------------------------------------ */
.asset-thumb {
  display: block; margin-top: 8px;
  max-width: 120px; max-height: 64px; border-radius: var(--r-sm);
  border: 1px solid var(--line); background: var(--bg);
  object-fit: contain;
}
/* The address is long and must stay copyable, but it cannot be allowed to set the width of
   the column and push the buttons off screen. */
.asset-url {
  display: block; max-width: 320px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 11.5px;
}

/* -- a row of actions at the end of a table row ----------------------------- */
/* The buttons were different sizes because some carried an icon and some did not, and each
   sized itself to its own content. They are laid out as one group instead: same height,
   same alignment, no wrapping, and the group hugs the right edge of the cell. */
.tbl td.actions { white-space: nowrap; }
.tbl td.actions .btn { vertical-align: middle; }
.row-actions {
  display: inline-flex; align-items: stretch; justify-content: flex-end; gap: 6px;
}
.row-actions .btn {
  height: 30px; padding: 0 11px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  white-space: nowrap;
}
/* An icon inside one of them must not make it taller than its neighbours. */
.row-actions .btn svg { width: 14px; height: 14px; flex: 0 0 14px; }

/* -- the phone field, lined up ---------------------------------------------- */
/* The +27 and the typed digits sat on different baselines: the prefix was centred on the
   box while the text sat on the input's own line box, and the two country/number controls
   were sized independently. Both are pinned to the same height and the same left edge. */
.phone-input { align-items: stretch; }
.phone-input .cpick-field,
.phone-input .input { height: 40px; }
.phone-dial {
  top: 0; bottom: 0; transform: none;
  display: flex; align-items: center;
  font-size: 13.5px; font-variant-numeric: tabular-nums;
}
/* Exactly the prefix width plus the input's own left padding, so the first typed digit
   starts where a digit would if the prefix were part of the text. */
.phone-number .input.has-dial { padding-left: 48px; }

/* -- picking a banner ------------------------------------------------------- */
.banner-pick { display: flex; align-items: center; gap: 12px; }
.banner-pick img {
  max-width: 200px; max-height: 70px; object-fit: contain;
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--bg);
}
.banner-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
  gap: 8px; margin-top: 12px;
}
.banner-tile {
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--bg);
  padding: 0; cursor: pointer; overflow: hidden; height: 54px;
}
.banner-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.banner-tile.is-on { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }

/* -- a colour ---------------------------------------------------------------- */
/* The native swatch is a different height in every browser, so it is pinned to the same
   height as the text field beside it. */
.colour-well {
  width: 44px; height: 30px; padding: 2px; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--panel);
}
.swatch-line { display: inline-flex; align-items: center; gap: 9px; }
.swatch {
  width: 18px; height: 18px; border-radius: 4px;
  border: 1px solid var(--line); display: inline-block;
}

/* A file input dressed as a button. `hidden` on the input keeps it out of the layout while
   the label stays a real, clickable control. */
label.btn { display: inline-flex; align-items: center; }

/* -- the segment rule builder ---------------------------------------------- */
.rule-match { display: flex; align-items: center; gap: 9px; font-size: 13px; margin-bottom: 14px; }
.rule-match .select { width: auto; }

/* Each rule is one line that wraps as a whole rather than mid-rule, so a narrow window
   never leaves an operator sitting on its own above its value. */
.rule {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 10px; margin-bottom: 8px;
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--r-sm);
}
.rule > .select { flex: 1 1 150px; min-width: 0; width: auto; }
.rule > .input  { flex: 1 1 150px; min-width: 0; }
.rule > .date-input { flex: 1 1 170px; min-width: 0; }
.rule > .btn { margin-left: auto; }
.rule-none { flex: 1 1 150px; font-size: 12.5px; color: var(--faint); }
.rule-pair { display: flex; gap: 8px; flex: 2 1 320px; min-width: 0; }
.rule-pair .select { flex: 1; min-width: 0; width: auto; }

/* A rule between two halves of a form that do different jobs. */
.rule-sep { height: 1px; background: var(--line); margin: 4px 0 20px; }

/* -- a bar saying what is currently being filtered out --------------------- */
/* Standing, and it names the rules rather than only showing a count — a page quietly
   showing 12 of 900 rows with nothing to say why is the thing this prevents. */
.filter-bar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 12px 20px 0; padding: 9px 13px;
  background: var(--accent-soft); border-radius: var(--r-sm);
  font-size: 12.5px; color: var(--ink);
}
.filter-bar .linkish { font-size: 12.5px; }

/* A row of small badges inside a table cell. */
.chip-row { display: inline-flex; flex-wrap: wrap; gap: 5px; }

/* -- a WhatsApp message, as it will look ----------------------------------- */
/* The bubble is not decoration: a template is judged by a reviewer reading it as a message,
   and a plain grey box does not show how a 900-character body actually lands. */
.wa-preview {
  white-space: pre-wrap; word-break: break-word;
  max-width: 380px; padding: 10px 13px;
  background: #dcf8c6; color: #111b21;
  border-radius: 10px 10px 10px 2px;
  font-size: 14px; line-height: 1.45;
  box-shadow: 0 1px 1px rgba(11, 20, 26, .13);
}

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

/* The chat runs to the bottom of the WINDOW rather than down the page.

   Everywhere else in this app the content column simply grows and the page scrolls, which
   is right for a table. It is wrong for a thread: the composer would sit below the fold of
   a two-year conversation, so replying would start with scrolling to the bottom. A class
   on <body> — the same trick the impersonation bar uses — because the rail is fixed and
   only the content column can be made to hold its own height. */
/* One expression for both cases: --imp-h is 0px unless the impersonation bar is up, so
   this is 100vh normally and 100vh-42px underneath the bar. Two rules drifted apart once
   already and this cannot.

   min-height matters as much as height. The base .shell rule carries min-height: 100vh,
   and min-height beats height — so setting the height alone left the shell a full viewport
   TALL while starting 42px down the page, hanging its bottom 42px below the fold. That is
   what cut the conversation list off. */
body.is-chat .shell   { height: calc(100vh - var(--imp-h)); min-height: 0; overflow: hidden; }
body.is-chat .main    { min-height: 0; }
body.is-chat .content { padding: 0; min-height: 0; display: flex; overflow: hidden; }

/* The topbar stops being sticky here, and that is a fix rather than a tidy-up.

   A sticky element's `top` is measured from its SCROLLPORT. Everywhere else in the app the
   scrollport is the viewport, so `top: var(--imp-h)` correctly parks the topbar just under
   the fixed impersonation bar. On this screen .shell becomes the scrollport — it is the
   thing with overflow — and .shell already starts below that bar. Sticky then applied the
   42px a second time: a blank band above the title, and the sub-nav sitting underneath an
   opaque topbar with only its bottom few pixels showing.

   Nothing scrolls in chat mode anyway. The thread has its own scroller and the frame is
   fixed, so there is nothing for the topbar to stick to. */
body.is-chat .topbar { position: static; }

.chat { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; }

/* -- the sub-nav ----------------------------------------------------------- */
/* One bar, two kinds of thing: questions about the inbox, then the shelves somebody made
   by hand. The divider is the only thing that says they are different, and that is enough
   — they are reached the same way and do the same job. */
.chat-subnav {
  display: flex; align-items: center; gap: 6px;
  padding: 10px 16px; overflow-x: auto; flex: 0 0 auto;
  background: var(--panel); border-bottom: 1px solid var(--line);
}
.chat-subnav-split {
  width: 1px; height: 20px; background: var(--line); flex: 0 0 auto; margin: 0 4px;
}
.chat-tab {
  display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  padding: 6px 12px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--bg); color: var(--muted);
  font: inherit; font-size: 12.5px; font-weight: 500;
  cursor: pointer; white-space: nowrap;
  transition: background .12s, border-color .12s, color .12s;
}
.chat-tab:hover { color: var(--ink); border-color: var(--accent); }
.chat-tab.is-on {
  background: var(--accent-soft); border-color: var(--accent); color: var(--accent);
}
.chat-tab .ico { width: 14px; height: 14px; }
.chat-tab-n { color: var(--faint); font-weight: 600; }
.chat-tab.is-on .chat-tab-n { color: var(--accent); }
.chat-tab-quiet { margin-left: auto; border-style: dashed; }

/* -- the three columns ----------------------------------------------------- */
.chat-body { display: flex; flex: 1; min-height: 0; min-width: 0; }

.chat-list {
  width: 330px; flex: 0 0 330px; min-height: 0;
  display: flex; flex-direction: column;
  background: var(--panel); border-right: 1px solid var(--line);
}
.chat-list-head { flex: 0 0 auto; border-bottom: 1px solid var(--line-soft); }
.chat-list-head .searchbar { padding: 12px 12px; }
.chat-list-scroll { flex: 1; min-height: 0; overflow-y: auto; }
.chat-list-empty { padding: 24px 18px; color: var(--muted); font-size: 13px; }

.chat-row {
  display: block; width: 100%; text-align: left;
  padding: 11px 13px; border: 0; border-bottom: 1px solid var(--line-soft);
  background: transparent; font: inherit; cursor: pointer;
  border-left: 3px solid transparent;
}
.chat-row:hover { background: var(--bg); }
.chat-row.is-on { background: var(--accent-soft); border-left-color: var(--accent); }
.chat-row-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.chat-row-top strong { font-size: 14px; font-weight: 500; min-width: 0;
                       overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-row-top strong.is-unread { font-weight: 700; }
.chat-row-when { color: var(--faint); font-size: 11px; flex: 0 0 auto; }
.chat-row-preview {
  color: var(--muted); font-size: 13px; margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-row-tags { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; margin-top: 6px; }
.chat-row-who { color: var(--faint); font-size: 11px; }

/* -- the thread ------------------------------------------------------------ */
.chat-thread { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.chat-thread-idle { align-items: center; justify-content: center; padding: 40px; }

.chat-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 16px; flex: 0 0 auto;
  background: var(--panel); border-bottom: 1px solid var(--line);
  /* Above the thread, so the edge below can be drawn over the messages passing under it. */
  position: relative; z-index: 1;
  transition: box-shadow .15s;
}
/* Only when there IS something above the fold.

   A thread opens at its newest message, so the first thing under this header is usually a
   bubble sliced through the middle of a sentence. That is correct — there really is more
   above — but a hard edge across the middle of a word reads as the top of the page having
   been cut off rather than as a conversation continuing upwards.

   A shadow was the first attempt and it was not enough: at the weight that does not look
   like a heavy toolbar, you cannot see it. Fading the messages out into the page colour
   as they pass under the header cannot be misread — nothing is ever sliced. It is drawn
   only when something is genuinely above, so a short conversation gets no false hint. */
.chat-head::after {
  content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 22px;
  background: linear-gradient(to bottom, var(--bg) 20%, rgba(247, 248, 242, 0));
  opacity: 0; transition: opacity .15s; pointer-events: none;
}
.chat-head.has-above::after { opacity: 1; }
.chat-head.has-above { box-shadow: 0 7px 11px -9px rgba(23, 37, 82, .45); }
.chat-head-who { min-width: 0; }
.chat-head-who strong { font-size: 14.5px; }
.chat-head-who .num { color: var(--muted); font-size: 13px; margin-left: 8px; }
.chat-head-on { color: var(--faint); font-size: 12px; margin-top: 2px; }
.chat-head-who strong { font-size: 14.5px; }
.chat-head-acts { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; flex-wrap: wrap;
                  justify-content: flex-end; }

/* Below about 1250px the name and five buttons stop fitting on one line, and squeezing them
   wraps the phone number into the middle of the buttons. Two rows is the honest answer. */
@media (max-width: 1250px) {
  .chat-head { flex-wrap: wrap; }
  .chat-head-who { flex: 1 1 100%; }
  .chat-head-acts { flex: 1 1 100%; justify-content: flex-start; }
}

/* Messages sit at the BOTTOM when there are not enough to fill the space, which is what
   every messaging app does and what makes a short thread read as a conversation rather
   than as a page that failed to load the rest. The auto margin on a pseudo-element is the
   whole trick: it eats the spare room when there is any and collapses to nothing when the
   thread overflows, so scrolling still works normally. */
.chat-scroll {
  flex: 1; min-height: 0; overflow-y: auto; padding: 16px 16px 8px;
  display: flex; flex-direction: column;
}
.chat-scroll::before { content: ''; margin-top: auto; }
.chat-scroll > * { flex: 0 0 auto; }
/* One element holding every message, so there is a single box whose height can be watched
   as images decide how tall they are. */
.chat-stack { display: flex; flex-direction: column; }

.chat-day { display: flex; justify-content: center; margin: 14px 0 10px; }
.chat-day > span {
  background: var(--panel); border: 1px solid var(--line); border-radius: 999px;
  padding: 2px 12px; font-size: 11.5px; color: var(--muted);
}

.chat-line { display: flex; justify-content: flex-start; margin-bottom: 2px; }
.chat-line.is-out { justify-content: flex-end; }
.chat-line.is-end { margin-bottom: 8px; }

.bubble {
  max-width: 70%; padding: 8px 11px; border-radius: var(--r);
  background: var(--panel); border: 1px solid var(--line);
  font-size: 13.5px;
}
.bubble.is-out { background: var(--accent-soft); border-color: #cfe7f4; }
/* The tail is drawn once per group, on the last bubble, so a run reads as one block
   rather than as five separate remarks. */
.bubble.is-grouped:not(.is-out) { border-top-left-radius: 4px; }
.bubble.is-grouped.is-out       { border-top-right-radius: 4px; }
.bubble.is-last:not(.is-out)    { border-bottom-left-radius: 3px; }
.bubble.is-last.is-out          { border-bottom-right-radius: 3px; }

.bubble-quote {
  border-left: 3px solid var(--faint); background: rgba(0, 0, 0, .04);
  border-radius: 6px; padding: 5px 8px; margin-bottom: 6px; font-size: 12.5px;
}
.bubble-quote.is-ours { border-left-color: var(--accent); }
.bubble-quote > span { display: block; color: var(--muted); font-size: 11px; margin-bottom: 1px; }
.bubble-quote > div {
  overflow: hidden; text-overflow: ellipsis;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.bubble-text { white-space: pre-wrap; word-break: break-word; }
.bubble-text em { color: var(--muted); }
.bubble-foot {
  display: flex; justify-content: flex-end; align-items: center; gap: 5px;
  margin-top: 4px; font-size: 11px; color: var(--muted);
}
.bubble-fail { font-size: 12px; color: var(--danger); margin-top: 4px; }
.bubble-img { display: block; margin-bottom: 6px; }
.bubble-img img { display: block; border-radius: 8px; max-width: 100%; max-height: 280px; }
.bubble-media { display: block; margin-bottom: 6px; max-width: 100%; border-radius: 8px; }
.bubble-file {
  display: inline-flex; align-items: center; gap: 8px; margin-bottom: 6px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel); color: var(--accent); font-size: 12.5px; text-decoration: none;
}
.tick-bad  { color: var(--danger); font-weight: 700; }
.tick-read { color: var(--accent); }

/* -- the composer ---------------------------------------------------------- */
.chat-compose {
  flex: 0 0 auto; padding: 12px 16px;
  background: var(--panel); border-top: 1px solid var(--line);
}
.chat-compose-row { display: flex; gap: 6px; align-items: flex-end; }
.chat-input { flex: 1; min-width: 0; resize: none; }
.chat-staged {
  display: flex; align-items: center; gap: 10px; margin-bottom: 8px;
  background: var(--accent-soft); border-radius: 9px; padding: 8px 10px;
}
.chat-staged img { width: 44px; height: 44px; object-fit: cover; border-radius: 6px; flex: 0 0 auto; }
.chat-staged > span { flex: 1; min-width: 0; font-size: 13px;
                      overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-staged em { color: var(--muted); font-style: normal; }

.chat-shut {
  background: var(--warn-soft); border: 1px solid var(--line);
  border-radius: var(--r); padding: 12px 14px;
}
.chat-shut strong { color: var(--warn); }
.chat-shut p { color: var(--muted); font-size: 13px; margin: 4px 0 10px; }

/* -- who they are ---------------------------------------------------------- */
.chat-who {
  width: 272px; flex: 0 0 272px; min-height: 0; overflow-y: auto;
  background: var(--panel); border-left: 1px solid var(--line);
  padding: 14px 14px 24px;
}
.chat-who-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.chat-who .shown { padding: 8px 0; border-bottom: 1px solid var(--line-soft); }
.chat-who .badge { margin: 0 4px 4px 0; }

/* -- picking one of many, inside a dialog ---------------------------------- */
/* A row that IS the button. Used where the choice is prose — a template's wording is the
   only thing that tells you which template it is, so it cannot be a <select>. */
.pick-row {
  display: block; width: 100%; text-align: left; font: inherit;
  padding: 10px 12px; margin-bottom: 8px; cursor: pointer;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
}
.pick-row:hover { border-color: var(--accent); background: var(--accent-soft); }
.pick-row strong { display: block; font-size: 13.5px; }
.pick-row > span { display: block; color: var(--muted); font-size: 13px; margin-top: 3px;
                   overflow: hidden; display: -webkit-box;
                   -webkit-line-clamp: 3; -webkit-box-orient: vertical; }

/* What the message will actually say, with the variables filled in. */
.tpl-preview {
  background: var(--bg); border: 1px solid var(--line-soft); border-radius: var(--r-sm);
  padding: 10px 12px; margin-bottom: 14px; font-size: 13px; white-space: pre-wrap;
}

/* A list inside a dialog where each row has its own buttons. */
.line-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 0; border-bottom: 1px solid var(--line-soft);
}
