/* WAScope — utilitarian SaaS inbox.
   Palette: ink #1e2b26 · ground #f5f7f6 (green-biased neutral) · panel #ffffff
   accent #0f7a54 · accent-soft #e4f2ec · warn #d97706 · danger #dc2626 · info #2f6fdb */

:root {
  --ink: #1e2b26;
  --ink-soft: #5b6b64;
  --tick-grey: #667781;   /* WhatsApp's own receipt grey */
  --tick-read: #0a7cc7;
  --ground: #f5f7f6;
  --panel: #ffffff;
  --line: #e2e8e5;
  --accent: #0f7a54;
  --accent-soft: #e4f2ec;
  --info-soft: #e6edfc;
  --warn-soft: #fdf0dd;
  --warn: #d97706;
  --danger: #dc2626;
  --info: #2f6fdb;
  --note: #fdf3d0;
  --radius: 8px;
  font-size: 14px;
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--ink);
  background: var(--ground);
  overflow: hidden;
}
button { font: inherit; cursor: pointer; }
button:disabled { cursor: not-allowed; opacity: 0.55; }
input, select, textarea { font: inherit; color: var(--ink); }

#app { display: flex; flex-direction: column; height: 100vh; }

/* ---------- top bar ---------- */
#topbar {
  height: 50px; flex: 0 0 50px;
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 16px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}
.brand { font-weight: 700; font-size: 16px; letter-spacing: -0.01em; }
.brand-dot { color: var(--accent); margin: 0 6px; }
.brand-sub { font-weight: 400; color: var(--ink-soft); font-size: 13px; }
.topbar-right { display: flex; align-items: center; gap: 14px; }
.badge {
  padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 600;
  background: var(--accent-soft); color: var(--accent);
}
.plan-badge {
  padding: 3px 12px; border-radius: 999px; font-size: 12px; font-weight: 700;
  border: 1px solid var(--line); background: var(--ground); color: var(--ink-soft);
}
.plan-badge.free { background: var(--ground); color: var(--ink-soft); }
.plan-badge.starter { background: #e4effc; color: var(--info); border-color: #c8dcf7; }
.plan-badge.pro { background: var(--accent-soft); color: var(--accent); border-color: #c4e6d6; }
.plan-badge.trial { background: #fdf0e0; color: var(--warn); border-color: #f5ddb8; }
.plan-badge:hover { filter: brightness(0.97); }
.me-wrap { display: flex; align-items: center; gap: 8px; }
.me-name { font-size: 13px; font-weight: 600; }
.me-label { font-size: 12px; color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.05em; }
.status-pill {
  border: 1px solid var(--line); border-radius: 999px; padding: 4px 10px;
  background: var(--panel); font-size: 12px; font-weight: 600;
}
.status-pill.online { color: var(--accent); border-color: var(--accent); }
.status-pill.offline { color: var(--ink-soft); }
.icon-btn { background: none; border: none; font-size: 16px; position: relative; padding: 4px; }
.notif-wrap { position: relative; }
.notif-count {
  position: absolute; top: -2px; inset-inline-end: -4px;
  background: var(--danger); color: #fff; font-size: 10px; font-weight: 700;
  border-radius: 999px; padding: 1px 5px;
}
.notif-panel {
  position: absolute; inset-inline-end: 0; top: 32px; width: 340px; max-height: 380px; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(30, 43, 38, 0.12); z-index: 60; padding: 6px;
}
.notif-item { padding: 10px 10px; font-size: 13px; line-height: 1.4; }
.notif-item + .notif-item { border-top: 1px solid var(--line); }
.notif-item:hover { background: var(--ground); }
/* Only rows that actually go somewhere invite a click. */
.notif-item.linked { cursor: pointer; }

/* An alert rule only ever raises a notification — it can never message a
   customer — so it is marked apart from the rules that can. */
.rule-kind.alert { background: var(--accent-soft); color: var(--accent); }

/* Settings → Notifications
   ------------------------------------------------------------------
   Built as rows rather than a table. A <table> of selects reads fine on a
   laptop and badly on a phone: the label column collapses to two words per
   line while the control column stays wide enough for its longest option, so
   the thing you are reading gets squeezed by the thing you are choosing. A
   flex row can simply stop being a row. */
.nt-lede { margin: 6px 0 18px; font-size: 13px; color: var(--ink-soft); max-width: 62ch; line-height: 1.5; }
.nt-card + .nt-card { margin-top: 16px; }
.nt-card-lede { margin: -2px 0 6px; font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; max-width: 62ch; }

/* Capped, because a control sitting 700px from the label it belongs to reads as
   an unrelated thing. Settings are a reading width, not a spreadsheet. */
.nt-card, .nt-lede { max-width: 860px; }

.nt-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px; padding: 12px 0; border-bottom: 1px solid var(--line);
}
.nt-row:last-child { border-bottom: 0; padding-bottom: 2px; }
.nt-row-text { min-width: 0; }          /* lets long hints wrap instead of pushing the control off */
.nt-row-name { font-size: 14px; font-weight: 600; }
.nt-row-hint { font-size: 12.5px; color: var(--ink-soft); margin-top: 3px; line-height: 1.5; max-width: 58ch; }
/* A flex container, not a plain box. `.switch` is a <label> — inline by default,
   so its 40x22 never applied and it collapsed to a sliver. It works everywhere
   else in the app only because it happens to sit inside a flex parent, which
   blockifies it into a flex item. */
.nt-row-control { flex: 0 0 auto; display: flex; align-items: center; justify-content: flex-end; }
.nt-select { min-width: 180px; }
.nt-note { margin: 14px 0 0; font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; max-width: 62ch; }

.nt-push-actions { margin-top: 12px; }

/* Phones: the control drops under its label and goes full width, so a select is
   never narrower than the option text it has to show. */
@media (max-width: 640px) {
  .nt-row { flex-direction: column; align-items: stretch; gap: 9px; }
  .nt-row-control { width: 100%; }
  .nt-select { width: 100%; min-width: 0; }
  .nt-row-control .btn { width: 100%; justify-content: center; }
  /* A switch keeps its own 40px — it has no intrinsic content (the input is
     0x0 and the slider is absolutely positioned), so anything resembling
     `width: auto` collapses it to a sliver. Only its box needs moving. */
  .nt-row-control:has(.switch) { width: auto; }
  .nt-lede, .nt-card-lede, .nt-row-hint, .nt-note { max-width: none; }
}

/* The mute chooser. A column so each duration is its own target rather than a
   row of buttons that collapse into each other on a phone. */
.mute-opts { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.mute-opts .btn { width: 100%; justify-content: center; }

/* A muted conversation dims its bell rather than hiding it — the control has to
   stay in the same place whether it is on or off. */
#mute-btn[data-muted="1"] { opacity: .55; }

@media (pointer: coarse) {
  .mute-opts .btn { min-height: 44px; }
  .nt-table select { min-height: 44px; font-size: 16px; }
}
.notif-item.unread { background: var(--accent-soft); }
.notif-item .when { color: var(--ink-soft); font-size: 11px; margin-top: 2px; }
.notif-head { display: none; }   /* desktop is a dropdown — no header/back needed */
/* On phones the bell isn't the right-most control, so a left-extending 340px
   dropdown runs off-screen. Make it a full-screen page that REPLACES the topbar
   (its own back + title become the only header), matching the app's other
   mobile views. */
@media (max-width: 640px) {
  .notif-panel {
    position: fixed; inset: 0; z-index: 70;   /* opaque, covers the topbar */
    width: auto; height: auto; max-height: none;
    border: none; border-radius: 0; box-shadow: none; padding: 0;
    display: flex; flex-direction: column;
  }
  .notif-head {
    display: flex; align-items: center; gap: 8px; flex: 0 0 auto;
    padding: 10px 12px; border-bottom: 1px solid var(--line);
  }
  .notif-back { background: none; border: none; font-size: 20px; line-height: 1; padding: 2px 6px; color: inherit; }
  .notif-title { font-weight: 700; font-size: 15px; }
  .notif-list { flex: 1 1 auto; overflow-y: auto; padding: 8px; }
}
.hidden { display: none !important; }

/* ---------- layout ---------- */
#main { flex: 1; display: flex; min-height: 0; }
/* THE LEFT BAR, LABELLED (UX plan, Phase 1): an icon AND a word on every
   button, grouped by thin separators. 80px holds a two-line label in every
   language we ship; a longer word wraps rather than being cut. */
#rail {
  flex: 0 0 80px; display: flex; flex-direction: column; align-items: stretch;
  gap: 2px; padding: 10px 4px; overflow-y: auto; scrollbar-width: none;
  background: var(--panel); border-inline-end: 1px solid var(--line);
}
.rail-btn, .rail-more, .rail-setup {
  display: flex; flex-direction: column; align-items: center; gap: 3px; width: 100%;
  padding: 7px 0 6px; border: none; background: none; border-radius: 10px;
  color: var(--ink-soft); font: inherit; cursor: pointer; flex: none;
}
.rail-btn:hover, .rail-more:hover, .rail-setup:hover { background: var(--ground); color: var(--ink); }
.rail-btn.active { background: var(--accent-soft); color: var(--accent); }
.rail-btn:focus-visible, .rail-more:focus-visible, .rail-setup:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rail-ico { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round; flex: none; }
.rail-lbl { font-size: 10.5px; font-weight: 600; line-height: 1.15; text-align: center; max-width: 100%;
  overflow-wrap: break-word; hyphens: auto; letter-spacing: -.1px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.rail-btn.active .rail-lbl { font-weight: 700; }
.rail-sep { height: 1px; background: var(--line); margin: 5px 8px; flex: none; }
.rail-gear { margin-top: auto; }
.rail-more { display: none; }
/* The setup checklist's progress, on the bar until it is done (syncRailSetup). */
.rail-setup { color: var(--accent); margin-bottom: 4px; }
.rail-ring { --p: 0; width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
  background: conic-gradient(var(--accent) calc(var(--p) * 1%), var(--accent-soft) 0); }
.rail-ring-in { width: 20px; height: 20px; border-radius: 50%; background: var(--panel); display: grid; place-items: center;
  font-size: 8.5px; font-weight: 800; font-variant-numeric: tabular-nums; }
/* The hover card: what a page is for, at once (no 1-second browser tooltip). */
.rail-hover { position: fixed; z-index: 400; max-width: 240px; padding: 8px 11px; border-radius: 9px;
  background: var(--ink); color: var(--panel); font-size: 12.5px; line-height: 1.4; pointer-events: none;
  box-shadow: 0 6px 20px rgba(0,0,0,.18); }
.rail-hover b { display: block; font-size: 13px; margin-bottom: 1px; }
/* The column that holds the health banner above whichever view is active. */
#stage { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
#view { flex: 1; display: flex; min-width: 0; min-height: 0; position: relative; }

/* ---------- inbox: chat list ---------- */
#chatlist {
  flex: 0 0 320px; width: 320px; min-width: 320px; max-width: 320px;
  display: flex; flex-direction: column; min-height: 0; position: relative;
  background: var(--panel); border-inline-end: 1px solid var(--line);
}
.chatlist-head { padding: 12px 12px 8px; display: flex; flex-direction: column; gap: 8px; }
.chatlist-top { display: flex; align-items: center; justify-content: space-between; }
.chatlist-title { font-size: 15px; font-weight: 700; color: var(--ink); }
.new-chat-btn {
  border: 1px solid var(--line); background: var(--panel); border-radius: 8px;
  width: 32px; height: 32px; display: grid; place-items: center; font-size: 15px;
}
.new-chat-btn:hover { background: var(--accent-soft); border-color: var(--accent); }

/* New-chat contact picker: slides over the chat list (WhatsApp left-panel UX) */
.newchat-panel {
  position: absolute; inset: 0; z-index: 6; background: var(--panel);
  display: flex; flex-direction: column; min-height: 0;
}
.newchat-panel.hidden { display: none; }
.newchat-top {
  display: flex; align-items: center; gap: 14px; flex: 0 0 auto;
  padding: 16px 14px; background: var(--accent); color: #fff;
}
.nc-back { border: none; background: none; color: #fff; font-size: 20px; line-height: 1; padding: 2px 4px; }
.newchat-title { font-size: 16px; font-weight: 700; }
.nc-searchwrap { padding: 8px 12px; }
.newchat-actions { display: flex; flex-direction: column; gap: 2px; padding: 6px 8px; }
.nc-action {
  display: flex; align-items: center; gap: 12px; text-align: start; border: none; background: none;
  padding: 9px 6px; border-radius: 8px; font-size: 14px; font-weight: 600; color: var(--ink);
}
.nc-action:hover { background: var(--ground); }
.nc-ico {
  width: 34px; height: 34px; flex: 0 0 34px; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent-soft); font-size: 15px;
}
.nc-body { flex: 1; min-height: 0; display: flex; border-top: 1px solid var(--line); }
.nc-list { flex: 1; min-width: 0; overflow-y: auto; scrollbar-gutter: stable; }
/* Invite-by-number box (checks WhatsApp registration) */
.nc-invite-box { padding: 6px 12px 10px; border-bottom: 1px solid var(--line); }
.nc-invite-box.hidden { display: none; }
.nc-invite-row { display: flex; gap: 6px; }
.nc-invite-row .search-input { flex: 1; }
.nc-invite-result { margin-top: 8px; font-size: 13px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.nc-ok { color: var(--accent); font-weight: 600; }
.nc-warn { color: var(--danger); font-weight: 600; }
.nc-group { padding-top: 2px; }
.nc-letter {
  position: sticky; top: 0; background: var(--panel); z-index: 1;
  font-size: 12px; font-weight: 700; color: var(--accent); padding: 6px 6px 2px;
}
.nc-row {
  display: flex; align-items: center; gap: 10px; padding: 7px 6px; border-radius: 8px; cursor: pointer;
  /* Skip painting off-screen rows so a long list loads and scrolls smoothly. */
  content-visibility: auto; contain-intrinsic-size: auto 48px;
}
.nc-row:hover { background: var(--ground); }
.nc-row-body { display: flex; flex-direction: column; min-width: 0; }
.nc-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nc-num { font-size: 12px; color: var(--ink-soft); }
.nc-fresh { font-style: italic; opacity: 0.8; }
.nc-row-self .avatar { color: #fff; }
/* "Contacts not on WhatsApp" section */
.nc-section-head {
  position: sticky; top: 0; z-index: 1; background: var(--ground);
  font-size: 12px; font-weight: 700; color: var(--ink-soft); text-transform: uppercase;
  letter-spacing: 0.04em; padding: 8px 12px; border-top: 1px solid var(--line);
}
.nc-off .nc-letter { color: var(--ink-soft); }
.nc-row-off { cursor: default; }
.nc-row-off .nc-row-body { flex: 1; }
.nc-invite-one { flex: 0 0 auto; }
.search-input {
  width: 100%; padding: 7px 10px; border: 1px solid var(--line); border-radius: 6px;
  background: var(--ground);
}
.filter-row { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.chip {
  padding: 3px 10px; border-radius: 999px; border: 1px solid var(--line);
  background: var(--panel); font-size: 12px; color: var(--ink-soft);
}
.chip.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.phone-filter { font-size: 12px; padding: 3px 6px; border: 1px solid var(--line); border-radius: 6px; max-width: 110px; }
.bulk-bar { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.bulk-count { font-size: 12px; font-weight: 700; color: var(--accent); margin-inline-end: 2px; }
.chat-check { align-self: center; accent-color: var(--accent); width: 15px; height: 15px; flex: 0 0 15px; }
/* Always show the scrollbar track (not just when the list overflows) so the rows
   never shift as search filters the list — the lane is reserved at all times,
   exactly like WhatsApp. overflow:scroll is honored on every browser; the
   scrollbar-gutter line is a modern belt-and-suspenders. */
.chat-items { flex: 1; overflow-y: scroll; scrollbar-gutter: stable; }
/* Thin, subtle scrollbar so the always-on track isn't a heavy grey bar. */
.chat-items::-webkit-scrollbar { width: 8px; }
.chat-items::-webkit-scrollbar-thumb { background: var(--line); border-radius: 999px; }
.chat-items::-webkit-scrollbar-track { background: transparent; }
.chat-items { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
.chat-item {
  display: flex; gap: 10px; padding: 10px 12px; cursor: pointer;
  border-bottom: 1px solid var(--ground);
}
.chat-item:hover { background: var(--ground); }
.chat-item.active { background: var(--accent-soft); }
.avatar {
  flex: 0 0 38px; width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center; font-weight: 700; color: #fff; font-size: 14px;
}
.avatar-xs {
  display: inline-grid; width: 22px; height: 22px; flex: 0 0 22px; font-size: 9px;
  vertical-align: middle; margin-inline-end: 4px;
}
.chat-item-body { flex: 1; min-width: 0; }
.chat-item-top { display: flex; align-items: baseline; gap: 6px; }
.chat-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-time { margin-inline-start: auto; font-size: 11px; color: var(--ink-soft); flex: 0 0 auto; }
.chat-preview {
  font-size: 12.5px; color: var(--ink-soft);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px;
}
.chat-item-meta { display: flex; align-items: center; gap: 5px; margin-top: 4px; flex-wrap: wrap; }
.mini-label { font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 999px; color: #fff; }
/* Access-label chips wrap within a bounded box so a member with many labels
   never stretches the Team row (was overflowing on both desktop and mobile). */
.mini-labels { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.team-table td[data-label="Access labels"] { max-width: 320px; }
.phone-tag { font-size: 10px; color: var(--ink-soft); border: 1px solid var(--line); padding: 1px 6px; border-radius: 4px; }
.unread-dot {
  margin-inline-start: auto; background: var(--accent); color: #fff; font-size: 11px; font-weight: 700;
  border-radius: 999px; padding: 1px 7px;
}
.sla-tag { font-size: 10px; font-weight: 700; color: var(--danger); }

/* ---------- inbox: conversation ---------- */
#conv { flex: 1; display: flex; flex-direction: column; min-width: 0; background: var(--ground); }
.conv-head {
  height: 54px; flex: 0 0 54px; display: flex; align-items: center; gap: 10px;
  padding: 0 16px; background: var(--panel); border-bottom: 1px solid var(--line);
}
.conv-title { font-weight: 700; }
.conv-sub { font-size: 12px; color: var(--ink-soft); }
.conv-head-right { margin-inline-start: auto; display: flex; gap: 8px; align-items: center; }
/* ---- suggested labels, under the conversation header ---- */
/* Ground, not panel: this is the machine talking, and it should read as a note
   attached to the conversation rather than as part of it. Wraps and scrolls
   rather than growing, so three suggestions cannot push the messages down. */
.lsug-bar { display: flex; flex-wrap: wrap; gap: 6px; padding: 7px 12px; background: var(--ground); border-bottom: 1px solid var(--line); max-height: 96px; overflow-y: auto; }
.lsug { display: flex; align-items: center; gap: 7px; background: var(--panel); border: 1px solid var(--line);
  border-radius: 999px; padding: 4px 6px 4px 10px; font-size: 12.5px;
  box-shadow: 0 1px 2px rgba(16,40,34,.04); }
@media (prefers-reduced-motion: no-preference) { .lsug { transition: box-shadow .12s ease, border-color .12s ease; } }
.lsug:hover { border-color: #cfe3da; box-shadow: 0 2px 6px rgba(16,40,34,.09); }
/* Where the verdict came from. A keyword is the operator's own word and cost
   nothing; a model guessed and was paid to. Reading a suggestion without
   knowing which is reading half of it. */
.lsug-src { font-size: 11px; opacity: 0.75; cursor: default; }
.lsug .btn.sm:focus-visible, .lb-auto input:focus-visible, .lb-stats .btn:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; }
.lsug-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 9px; }
.lsug-name { font-weight: 600; }
/* The evidence. Without it a suggestion is an opinion; with it, it is checkable. */
.lsug-why { color: var(--ink-soft); font-size: 11.5px; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lsug .btn.sm { padding: 2px 9px; font-size: 12px; }
.conv-body { flex: 1; min-height: 0; position: relative; display: flex; flex-direction: column; }
.msgs { flex: 1; overflow-y: auto; padding: 18px 22px; display: flex; flex-direction: column; gap: 8px; }
/* WhatsApp-style "jump to latest" — appears when the reader has scrolled up; the
   badge counts messages that arrived while they were away. */
.jump-latest {
  position: absolute; inset-inline-end: 18px; bottom: 16px; z-index: 5;
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  min-width: 40px; height: 40px; padding: 0 12px;
  background: var(--panel); color: var(--ink); border: 1px solid var(--line);
  border-radius: 999px; box-shadow: 0 4px 14px rgba(20, 28, 25, 0.22);
  font-size: 18px; font-weight: 700; cursor: pointer;
}
.jump-latest:hover { border-color: var(--accent); color: var(--accent); }
/* The floating @, stacked above the ↓ like WhatsApp's: same shape, green, so
   it reads as the chat list's @ badge. */
.jump-mention {
  position: absolute; inset-inline-end: 18px; bottom: 66px; z-index: 5;
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  min-width: 40px; height: 40px; padding: 0 12px;
  background: var(--accent); color: #fff; border: 1px solid var(--accent);
  border-radius: 999px; box-shadow: 0 4px 14px rgba(20, 28, 25, 0.22);
  font-size: 17px; font-weight: 700; cursor: pointer;
}
.jump-mention:hover { filter: brightness(1.08); }
.jump-mention .jump-count { background: #fff; color: var(--accent); }
/* "N unread messages", where this opening of the chat lands. */
.unread-divider { align-self: stretch; text-align: center; margin: 8px 0; }
.unread-divider span {
  display: inline-block; font-size: 12px; font-weight: 600; color: var(--ink);
  background: var(--panel); border: 1px solid var(--line); border-radius: 999px; padding: 3px 12px;
}
.jump-count {
  background: var(--accent); color: #fff; border-radius: 999px; padding: 0 6px;
  min-width: 18px; text-align: center; font-size: 11px; font-weight: 700; line-height: 18px;
}
/* Each message sits in a full-width row so hovering anywhere in the row (not
   just the bubble) reveals the reaction trigger, like WhatsApp. */
.msg-row { display: flex; width: 100%; }
.msg-row.out { justify-content: flex-end; }
.msg { max-width: 62%; position: relative; }
.msg.out { align-self: flex-end; }

/* WhatsApp-style reaction trigger: appears on message hover, in the gutter. */
.react-trigger {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 30px; height: 30px; border-radius: 50%;
  border: 1px solid var(--line); background: var(--panel);
  box-shadow: 0 1px 5px rgba(0,0,0,.14); cursor: pointer;
  font-size: 15px; line-height: 1; display: flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none; transition: opacity .12s ease;
}
.msg:not(.out) .react-trigger { inset-inline-end: -38px; }
.msg.out .react-trigger { inset-inline-start: -38px; }
.msg-row:hover .react-trigger { opacity: 1; pointer-events: auto; }
.react-trigger { color: var(--ink-soft); }
.react-trigger svg { display: block; }
.react-trigger:hover { background: var(--ground); color: var(--accent); }

/* WhatsApp-style dropdown chevron in the bubble's top-right corner (hover). */
.msg-menu-btn {
  position: absolute; top: 2px; inset-inline-end: 3px; padding: 0;
  width: 24px; height: 22px; border: none; border-radius: 6px;
  background: transparent; color: var(--ink-soft); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity .12s ease, background .12s ease;
}
.msg-menu-btn svg { display: block; }
.msg-row:hover .msg-menu-btn { opacity: .75; }
.msg-menu-btn:hover { opacity: 1; background: rgba(0, 0, 0, .07); }

/* Reaction picker popover (quick emojis + searchable grid) */
.react-pop { position: fixed; z-index: 95; background: var(--panel); border: 1px solid var(--line);
  border-radius: 999px; box-shadow: 0 10px 30px rgba(30,43,38,.20); padding: 5px 6px; }
.react-pop.hidden { display: none; }
.react-pop.searching { border-radius: 14px; }
.rp-quick { display: flex; align-items: center; gap: 3px; }
.rp-emoji { border: none; background: none; font-size: 22px; line-height: 1; padding: 4px 6px;
  border-radius: 10px; cursor: pointer; transition: transform .08s ease, background .1s ease; }
.rp-emoji:hover { background: var(--ground); transform: scale(1.25); }
.rp-emoji.on { background: var(--accent-soft); }
.rp-more { border: none; background: var(--ground); color: var(--ink-soft); border-radius: 50%;
  width: 32px; height: 32px; font-size: 18px; cursor: pointer; margin-inline-start: 3px; }
.rp-more:hover { background: var(--line); }
.rp-search { width: 300px; padding: 4px; }
.rp-search input { width: 100%; border: 1px solid var(--line); border-radius: 8px; padding: 8px 11px;
  margin-bottom: 6px; font: inherit; background: var(--ground); }
.rp-search input:focus { outline: none; border-color: var(--accent); background: var(--panel); }
.rp-grid { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 1px; max-height: 210px; overflow-y: auto; overflow-x: hidden; }
.rp-grid .rp-emoji { width: 100%; }
.rp-empty { padding: 16px; text-align: center; color: var(--ink-soft); font-size: 13px; grid-column: 1 / -1; }
.bubble {
  position: relative; padding: 8px 12px; border-radius: 10px;
  background: var(--panel); border: 1px solid var(--line);
  line-height: 1.45; word-wrap: break-word; white-space: pre-wrap;
}
.msg.out .bubble { background: #dcf3e8; border-color: #c4e6d6; }
/* Clickable links in message text — readable on both the white incoming bubble
   and the green outgoing one; break long URLs so they never overflow. */
.msg-link { color: #027eb5; text-decoration: underline; word-break: break-word; }
.msg-link:hover { text-decoration: none; }
.msg.out .msg-link { color: #036a4e; }
.msg .bubble.auto { background: #eef2ff; border-color: #dfe6fb; }
.msg .bubble.note { background: var(--note); border-color: #f0dfa0; }
.msg-author { font-size: 11px; font-weight: 700; color: var(--accent); margin-bottom: 2px; }
/* Clickable @mention inside a message bubble → opens that person's chat. */
.mention { color: var(--accent); font-weight: 600; cursor: pointer; border-radius: 4px; padding: 0 1px; }
.mention:hover { text-decoration: underline; background: var(--accent-soft); }
.msg-meta { display: flex; gap: 6px; font-size: 10.5px; color: var(--ink-soft); margin-top: 3px; padding: 0 4px; }
.msg.out .msg-meta { justify-content: flex-end; }
.msg-flag { color: var(--danger); font-weight: 700; }
/* Reactions overlap the bubble's bottom edge. The bubble is position:relative
   (a positioned element) so it paints above static siblings — the chips must be
   positioned too, with a z-index, to sit ON TOP of the bubble, not behind it. */
.msg-reactions {
  display: flex; gap: 4px; flex-wrap: wrap;
  margin: -10px 0 2px; padding: 0 8px;
  position: relative; z-index: 1;
}
.msg.out .msg-reactions { justify-content: flex-end; }
.reaction {
  display: inline-flex; align-items: center; font-size: 12px; line-height: 1.5;
  background: var(--panel); border: 1px solid var(--line); border-radius: 999px;
  padding: 1px 7px; box-shadow: 0 1px 2px rgba(0,0,0,.12); cursor: default;
}
/* Group chats collapse reactions to distinct emoji(s) + a total count; the chip
   is a button that opens the member breakdown. */
.reaction-group { gap: 3px; cursor: pointer; font: inherit; color: inherit; }
.reaction-group:hover { background: var(--ground); }
.reaction-count { font-size: 11px; font-weight: 700; color: var(--ink-soft); }
.react-detail {
  position: fixed; z-index: 60; min-width: 150px; max-height: 240px; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 6px 24px rgba(0,0,0,.18); padding: 6px;
}
.react-detail.hidden { display: none; }
.rd-head { font-size: 11px; color: var(--ink-soft); padding: 2px 8px 6px; border-bottom: 1px solid var(--line); margin-bottom: 4px; }
.rd-row { display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-radius: 6px; }
.rd-row:hover { background: var(--ground); }
.rd-emoji { font-size: 16px; line-height: 1; }
.rd-name { font-size: 13px; color: var(--ink); }
.day-divider { align-self: center; font-size: 11px; color: var(--ink-soft); background: var(--panel); border: 1px solid var(--line); padding: 2px 10px; border-radius: 999px; margin: 6px 0; }

/* Quoted message shown inside a reply bubble */
.quoted {
  display: flex; gap: 8px; align-items: stretch; cursor: pointer;
  border-inline-start: 3px solid var(--accent); background: rgba(15,122,84,.06);
  border-radius: 5px; padding: 4px 8px; margin-bottom: 5px; font-size: 12.5px; line-height: 1.35;
}
.quoted:hover { background: rgba(15,122,84,.12); }
.quoted-body { min-width: 0; flex: 1; display: flex; flex-direction: column; justify-content: center; }
.quoted-author { display: block; font-weight: 700; color: var(--accent); font-size: 11.5px; }
.quoted-text { color: var(--ink-soft); white-space: pre-wrap;
  overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
/* Small image/video preview of the replied-to message (WhatsApp-style). The
   emoji fallback shows until hydrateQuotedThumbs() paints the real thumbnail. */
.quoted-thumb {
  flex: none; width: 38px; align-self: center; aspect-ratio: 1; border-radius: 5px; overflow: hidden;
  background-size: cover; background-position: center; background-color: var(--line);
  display: grid; place-items: center; font-size: 15px;
}
.quoted-thumb.loaded .quoted-thumb-ico { display: none; }

/* A shared WhatsApp contact (vCard), like WhatsApp's own bubble: photo + name,
   then Message / Save contact (or Invite). Several contacts: stacked photos +
   "View all". Tapping the card opens the details panel (.ccm-*). */
.ccard-list { min-width: 230px; max-width: 300px; white-space: normal; }
.ccard-head { display: flex; align-items: center; gap: 10px; cursor: pointer; border-radius: 6px; }
.ccard-head:hover .ccard-name { text-decoration: underline; }
.ccard-av { color: #fff; background-size: cover; background-position: center; }
.ccard-av.has-photo { color: transparent; }
.ccard-stack { display: flex; flex: none; }
.ccard-stack .ccard-av { border: 2px solid var(--panel); box-sizing: content-box; }
.ccard-stack .ccard-av + .ccard-av { margin-inline-start: -14px; }
.ccard-info { min-width: 0; flex: 1; }
.ccard-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ccard-num { font-size: 12px; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ccard-actions { display: flex; margin-top: 8px; border-top: 1px solid var(--line); }
.ccard-actions > * { flex: 1; text-align: center; padding: 7px 4px 1px; font-size: 13px; }
.ccard-actions > * + * { border-inline-start: 1px solid var(--line); }
.ccard-btn { background: none; border: 0; color: var(--accent); cursor: pointer; font: inherit; font-size: 13px; font-weight: 600; }
.ccard-btn:hover { text-decoration: underline; }
.ccard-btn:disabled { cursor: default; text-decoration: none; opacity: .7; }
.ccard-done { color: var(--ink-soft); font-weight: 600; }
.msg.out .ccard-actions, .msg.out .ccard-actions > * + * { border-color: #c4e6d6; }
.msg.out .ccard-stack .ccard-av { border-color: #dcf3e8; }
.ccm-list { display: flex; flex-direction: column; gap: 14px; max-height: 60vh; overflow-y: auto; }
.ccm-card + .ccm-card { border-top: 1px solid var(--line); padding-top: 14px; }
.ccm-card .ccard-head { cursor: default; margin-bottom: 6px; }
.ccm-card .ccard-head:hover .ccard-name { text-decoration: none; }
.ccm-phone { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 0; padding-inline-start: 62px; }
.ccm-num { font-size: 14px; }
.ccm-sub { font-size: 12px; color: var(--ink-soft); }
.ccm-link { background: none; border: 0; color: var(--accent); font: inherit; font-weight: 600; cursor: pointer; }
.ccm-actions { display: flex; gap: 8px; align-items: center; padding-top: 6px; padding-inline-start: 62px; }
.ccm-saved { color: var(--ink-soft); font-weight: 600; font-size: 13px; }

/* "Replying to" banner above the composer */
.reply-banner {
  display: flex; align-items: center; gap: 8px; background: var(--ground);
  border-inline-start: 3px solid var(--accent); border-radius: 6px; padding: 6px 8px; margin-bottom: 8px;
}
.reply-banner-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.reply-banner-author { font-size: 12px; font-weight: 700; color: var(--accent); }
.reply-banner-text { font-size: 12.5px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.reply-banner-x { border: none; background: none; color: var(--ink-soft); font-size: 15px; cursor: pointer; padding: 2px 6px; border-radius: 5px; }
.reply-banner-x:hover { background: var(--line); }

/* When you jump to a message (search hit or reply preview), highlight the WHOLE
   row: its background pulses to the accent tint, holds ~a second, then fades —
   so it clearly reads as "this is the one you selected". The bubble also gets a
   soft ring for extra emphasis. */
.msg-row.msg-flash { animation: rowFlash 1.6s ease; border-radius: 10px; }
@keyframes rowFlash {
  0%   { background: transparent; }
  12%  { background: var(--accent-soft); }
  65%  { background: var(--accent-soft); }
  100% { background: transparent; }
}
.msg-row.msg-flash .bubble { animation: bubbleFlash 1.6s ease; }
@keyframes bubbleFlash {
  0%, 55% { box-shadow: 0 0 0 2px var(--accent); }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* Arriving from the briefing — see markLanded(). Held rather than pulsed: the
   1.6s flash above confirms a bubble you tapped yourself, this one has to answer
   "which of these hundred messages am I here about" to somebody who did not pick
   the chat, the message, or the moment. Four seconds, then it fades. */
.msg-row.msg-landed { animation: rowLanded 5s ease forwards; border-radius: 10px; }
@keyframes rowLanded {
  0%   { background: transparent; }
  4%   { background: var(--accent-soft); }
  80%  { background: var(--accent-soft); }
  100% { background: transparent; }
}
.msg-row.msg-landed .bubble { animation: bubbleLanded 5s ease forwards; }
@keyframes bubbleLanded {
  0%, 80% { box-shadow: 0 0 0 2px var(--accent); }
  100% { box-shadow: 0 0 0 0 transparent; }
}

.composer { flex: 0 0 auto; padding: 10px 16px 14px; background: var(--panel); border-top: 1px solid var(--line); }
.composer-row { display: flex; gap: 8px; align-items: flex-end; }
.composer-input-wrap { position: relative; flex: 1; display: flex; }
.composer textarea {
  flex: 1; width: 100%; resize: none; border: 1px solid var(--line); border-radius: 22px;
  padding: 11px 16px; min-height: 44px; max-height: 130px; background: var(--ground);
  font: inherit; line-height: 1.4; transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
}
.composer textarea:focus {
  outline: none; background: var(--panel); border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(15, 122, 84, .14);
}
.composer textarea.note-mode { background: var(--note); }
.composer-side { display: flex; flex-direction: column; gap: 6px; }
.send-btn {
  background: var(--accent); color: #fff; border: none; border-radius: 22px;
  padding: 11px 22px; min-height: 44px; font-weight: 700; white-space: nowrap;
  transition: filter .15s ease, transform .05s ease;
}
.send-btn:hover { filter: brightness(1.08); }
.send-btn:active { transform: translateY(1px); }
.composer-tools { display: flex; align-items: center; gap: 10px; margin-top: 6px; font-size: 12px; color: var(--ink-soft); flex-wrap: wrap; }
.note-toggle { border: 1px solid var(--line); background: var(--panel); border-radius: 999px; padding: 3px 10px; font-size: 12px; }
.note-toggle.on { background: var(--note); border-color: #e5c95d; font-weight: 700; }
.from-select { font-size: 12px; border: 1px solid var(--line); border-radius: 6px; padding: 3px 6px; }
.ai-btn {
  border: 1px solid var(--accent); color: var(--accent); background: var(--panel);
  border-radius: 999px; padding: 3px 12px; font-size: 12px; font-weight: 600;
}
.ai-btn:hover { background: var(--accent-soft); }

/* composer autocomplete (quick replies / mentions) */
.ac-menu {
  position: absolute; bottom: calc(100% + 6px); inset-inline-start: 0; width: min(420px, 100%);
  background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(30, 43, 38, 0.15); z-index: 70; padding: 4px;
  max-height: 240px; overflow-y: auto;
}
.ac-item {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  width: 100%; text-align: start; background: none; border: none;
  padding: 7px 10px; border-radius: 6px; font-size: 13px;
}
.ac-item:hover, .ac-item.sel { background: var(--accent-soft); }
.ac-item b { color: var(--accent); }
.ac-sub { font-size: 11.5px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }

/* ---------- inbox: details panel ---------- */
/* No padding on the SCROLLER itself. A sticky child pins below the scroll
   container's padding, not above it, so 16px of padding here left a permanently
   transparent band at the top of the pane that the content scrolled visibly
   through, just above the sticky tab bar. The padding moves to the children,
   where it scrolls with them and nothing can slide underneath. */
#details {
  flex: 0 0 300px; overflow-y: auto;
  background: var(--panel); border-inline-start: 1px solid var(--line); padding: 0;
}
/* The Info tab is full-bleed so card dividers reach both edges; Properties keeps
   the old gutter. Padding stays on the CHILDREN, never on #details — see above. */
.det-tabbody { padding: 0 16px 16px; }
.det-info { padding-block-end: 16px; }
#details > .empty { padding: 16px; }
/* ---- in-chat message search (takes over the right details column) ---- */
#details:has(.chat-search) { padding: 0; }
.chat-search { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.cs-head { position: relative; display: flex; align-items: center; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--line); }
.cs-x { background: none; border: none; font-size: 16px; color: var(--ink-soft); cursor: pointer; padding: 2px 6px; border-radius: 6px; line-height: 1; }
.cs-x:hover { background: var(--line); color: var(--ink); }
.cs-title { font-weight: 700; font-size: 14px; }
.cs-cal { margin-inline-start: auto; display: inline-flex; align-items: center; background: none; border: none;
  color: var(--ink-soft); cursor: pointer; padding: 4px 6px; border-radius: 6px; }
.cs-cal:hover { background: var(--line); color: var(--accent); }
/* Invisible native date input, parked under the calendar icon so showPicker()
   opens the calendar right there. */
.cs-date-hidden { position: absolute; inset-inline-end: 12px; top: 14px; width: 22px; height: 22px; opacity: 0; pointer-events: none; }
/* The outer pill carries the border; the input itself stays outline-free. On
   focus the PILL border turns accent (via :focus-within), so the highlight is on
   the whole bar, not a ring around the input. */
.cs-inputwrap { display: flex; align-items: center; gap: 8px; margin: 12px 14px 6px; padding: 8px 10px; background: var(--ground); border: 1px solid var(--line); border-radius: 9px; }
.cs-inputwrap:focus-within { border-color: var(--accent); }
.cs-ico { color: var(--ink-soft); flex: none; }
.cs-input { flex: 1; min-width: 0; border: none; background: none; outline: none; font-size: 13.5px; color: var(--ink); }
/* Beat the global :focus-visible outline rule so typing shows no focus ring. */
.cs-input:focus, .cs-input:focus-visible { outline: none; box-shadow: none; }
.cs-clear { background: none; border: none; color: var(--ink-soft); font-size: 13px; cursor: pointer; padding: 0 4px; border-radius: 5px; flex: none; }
.cs-clear:hover { background: var(--line); color: var(--ink); }
.cs-results { flex: 1; min-height: 0; overflow-y: auto; padding: 4px 0 12px; }
.cs-hint { padding: 22px 18px; color: var(--ink-soft); font-size: 12.5px; text-align: center; line-height: 1.5; }
.cs-group-h { position: sticky; top: 0; z-index: 1; background: var(--panel); padding: 8px 16px 4px;
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--accent); }
.cs-item { display: block; width: 100%; text-align: start; background: none; border: none;
  border-bottom: 1px solid var(--line); padding: 9px 16px; cursor: pointer; }
.cs-item:hover { background: var(--accent-soft); }
.cs-item.active { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
.cs-item-top { display: flex; justify-content: space-between; gap: 8px; margin-bottom: 2px; }
.cs-item-who { font-size: 12px; font-weight: 700; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-item-time { font-size: 11px; color: var(--ink-soft); flex: none; }
.cs-item-body { font-size: 12.5px; color: var(--ink-soft); line-height: 1.4;
  overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.cs-item-body mark { background: #fce7a1; color: inherit; padding: 0 1px; border-radius: 2px; }

.details-section { margin-bottom: 18px; }
.details-section h4 {
  margin: 0 0 8px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--ink-soft);
}
.detail-kv { display: flex; justify-content: space-between; font-size: 13px; padding: 3px 0; }
.detail-kv .k { color: var(--ink-soft); }
/* Group members list in the details panel: total count badge + scrollable roster. */
.members-count {
  display: inline-block; min-width: 18px; text-align: center; font-size: 11px; font-weight: 700;
  color: var(--panel); background: var(--ink-soft); border-radius: 999px; padding: 0 6px; margin-inline-start: 4px;
}
.members-list { max-height: 240px; overflow-y: auto; border: 1px solid var(--line); border-radius: 8px; }
.member-row {
  display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  font-size: 13px; padding: 5px 9px; border-bottom: 1px solid var(--line);
}
.member-row:last-child { border-bottom: 0; }
.member-name { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.member-num { color: var(--ink-soft); font-size: 12px; flex-shrink: 0; }
.detail-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.label-chips { display: flex; flex-wrap: wrap; gap: 6px; }

/* ============ Custom properties ============ */
/* Settings manager */
.cp-sections { display: flex; flex-direction: column; gap: 14px; margin: 14px 0; }
.cp-section { border: 1px solid var(--line); border-radius: 10px; background: var(--panel); overflow: hidden; }
.cp-section-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--line); background: var(--ground); }
.cp-sec-name { flex: 1; font-weight: 700; font-size: 14px; border: 1px solid transparent; border-radius: 6px; padding: 4px 6px; background: none; color: var(--ink); }
.cp-sec-name:focus { outline: none; border-color: var(--line); background: var(--panel); }
.cp-del { color: var(--ink-soft); }
.cp-del:hover { color: var(--danger); }
.cp-props { padding: 6px 12px 10px; }
.cp-prop { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--ground); font-size: 13px; }
.cp-prop:last-child { border-bottom: none; }
.cp-prop-name { font-weight: 600; color: var(--ink); }
.cp-type { font-size: 12px; color: var(--ink-soft); }
.cp-req { font-size: 10.5px; font-weight: 700; color: var(--warn); background: #fdf0e0; padding: 1px 6px; border-radius: 4px; }
.cp-detail { font-size: 12px; color: var(--ink-soft); }
.cp-prop-actions { margin-inline-start: auto; display: flex; align-items: center; gap: 6px; }
.cp-empty { font-size: 12.5px; color: var(--ink-soft); padding: 8px 0; }

/* Property editor modal */
.pm-options { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.pm-opt { display: flex; gap: 6px; align-items: center; }
.pm-opt input { flex: 1; padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--ground); }
.pm-tree { display: flex; flex-direction: column; gap: 5px; margin-bottom: 8px; max-height: 260px; overflow-y: auto; }
.pm-node-row { display: flex; gap: 5px; align-items: center; }
.pm-node-row input { flex: 1; padding: 5px 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--ground); font-size: 13px; }
.pm-node { margin-bottom: 3px; }

/* Chat details: property values */
.cp-val-section { margin-bottom: 12px; }
.cp-val-sec-name { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-soft); font-weight: 700; margin-bottom: 6px; }
.cp-field { display: flex; flex-direction: column; gap: 3px; margin-bottom: 9px; }
.cp-field-lab { font-size: 12.5px; color: var(--ink-soft); }
.cp-req-dot { color: var(--warn); }
.cp-input { width: 100%; padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--ground); color: var(--ink); font-size: 13px; }
.cp-input:focus { outline: none; border-color: var(--accent); }
.cp-multi { display: flex; flex-wrap: wrap; gap: 5px; }
.cp-chip { font-size: 12px; padding: 3px 9px; border-radius: 999px; border: 1px solid var(--line); background: var(--ground); color: var(--ink-soft); cursor: pointer; }
.cp-chip.on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); font-weight: 600; }
.cp-none { font-size: 12.5px; color: var(--ink-soft); }
.cp-dep-wrap { display: flex; flex-wrap: wrap; gap: 6px; }
.cp-dep { flex: 1 1 30%; min-width: 90px; padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--ground); font-size: 12.5px; }
.cp-file { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cp-file-link { color: var(--accent); text-decoration: none; font-size: 13px; word-break: break-all; }
.cp-file-link:hover { text-decoration: underline; }
.cp-file-btn { cursor: pointer; }

/* Details panel tabs (Info | Properties) */
/* Carries its own horizontal padding now that the pane has none, so the bar and
   its border reach both edges and cover the full width while pinned. */
.det-tabs {
  display: flex; gap: 4px; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; background: var(--panel); z-index: 2;
  margin: 0; padding: 4px 10px 0 16px; align-items: stretch;
}
.det-tab {
  flex: 1; padding: 10px 8px; border: none; background: none; cursor: pointer;
  font-size: 13px; font-weight: 600; color: var(--ink-soft); border-bottom: 2px solid transparent;
}
/* Pin / Archive / Mask: chat-level operations, so they sit with the chat's
   title instead of costing a titled section in the body. */
.det-more {
  flex: none; align-self: center; border: none; background: none; cursor: pointer;
  color: var(--ink-soft); font-size: 16px; line-height: 1;
  padding: 4px 8px; border-radius: 6px; margin-block-end: 4px;
}
.det-more:hover, .det-more[aria-expanded="true"] { background: var(--ground); color: var(--ink); }
.det-menu {
  display: flex; flex-direction: column; gap: 2px;
  padding: 6px 10px; background: var(--ground); border-block-end: 1px solid var(--line);
}
.det-menu[hidden] { display: none; }
.det-menu button {
  border: none; background: none; text-align: start; cursor: pointer;
  font-size: 12.5px; color: var(--ink); padding: 7px 6px; border-radius: 6px;
}
.det-menu button:hover { background: var(--panel); }

/* ---- identity: a header, not a key/value record ---- */
.det-id { display: flex; gap: 10px; align-items: flex-start; padding: 13px 16px 11px; }
.det-id-av { flex: none; width: 34px; height: 34px; font-size: 12.5px; }
.det-id-main { min-width: 0; }
.det-id-name {
  font-size: 14.5px; font-weight: 700; line-height: 1.25;
  overflow-wrap: anywhere;
}
.det-id-num { display: flex; align-items: center; gap: 2px; font-size: 12.5px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.det-copy {
  border: none; background: none; cursor: pointer; color: var(--ink-soft);
  font-size: 11.5px; padding: 2px 4px; border-radius: 4px; line-height: 1;
}
.det-copy:hover { background: var(--ground); color: var(--accent); }
.det-id-via { font-size: 11.5px; color: var(--ink-soft); }
.det-wait {
  margin: 0 16px 12px; padding: 5px 9px; border-radius: 6px;
  font-size: 11.5px; color: #92400e; background: #fff7ed; border: 1px solid #fed7aa;
}
.det-none { font-size: 12px; color: var(--ink-soft); }

/* ---- the ownership strip: the two controls a shared inbox touches most ---- */
.det-strip {
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 16px; background: var(--ground);
  border-block: 1px solid var(--line);
}
.det-assign { display: flex; align-items: center; gap: 8px; }
.det-strip-label {
  flex: none; font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-soft);
}
.det-assign .assignee-select { flex: 1; min-width: 0; margin: 0; }
.det-labels { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.label-tag {
  font-size: 11.5px; color: #fff; padding: 3px 9px; border-radius: 999px;
  max-width: 100%; overflow-wrap: anywhere;
}
/* +N / Less — solid border, so it reads as "there is more here", distinct from
   the dashed ＋ Label which reads as "add something new". */
.det-labels-more {
  font-size: 11.5px; font-weight: 700; padding: 3px 9px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line); background: var(--panel); color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
.det-labels-more:hover { border-color: var(--accent); color: var(--accent); }
.det-addlabel {
  font-size: 11.5px; padding: 3px 9px; border-radius: 999px; cursor: pointer;
  border: 1px dashed var(--line); background: var(--panel); color: var(--ink-soft);
}
.det-addlabel:hover, .det-addlabel[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }
.det-labelpick {
  display: flex; flex-direction: column; gap: 7px;
  padding: 9px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel);
}
.det-labelpick[hidden] { display: none; }
.det-labelpick-hint { font-size: 11.5px; color: var(--ink-soft); }
/* Create-a-label, inside the picker. Separated by a rule rather than a gap:
   picking an existing label and making a new one are different actions and the
   list above can be long. */
.det-labelnew {
  display: flex; gap: 6px; align-items: center;
  padding-block-start: 8px; border-block-start: 1px solid var(--line);
}
.det-labelnew-in {
  flex: 1; min-width: 0; padding: 5px 8px; font-size: 12.5px;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg); color: var(--ink);
}
.det-labelnew-in:focus { outline: none; border-color: var(--accent); }
.det-labelnew-in:disabled { opacity: .6; }
.det-labelnew .btn { flex: none; }

/* ---- collapsible cards ---- */
.det-card { border-block-end: 1px solid var(--line); }
/* relative, because the deal switcher's menu hangs off it */
.det-cardrow { display: flex; align-items: center; position: relative; }

/* ---- deal switcher: the summary, made clickable ----
   A conversation can carry several live deals. The header names the one the
   stage buttons act on, so it has to be the thing you click to change it. */
/* Both the head and the switcher want the row's spare width, and splitting it
   evenly left "Negotiating" as "Negotiat…". The head only needs its caret and
   title, so when a switcher is present it stops competing. */
.det-cardrow:has(.det-cardswitch) .det-cardhead { flex: 0 1 auto; }
.det-cardswitch {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 6px;
  justify-content: flex-end; cursor: pointer;
  border: 1px solid transparent; border-radius: 6px; background: none;
  padding: 3px 6px; margin-inline-end: 6px; color: var(--ink-soft); font-size: 12px;
}
.det-cardswitch:hover, .det-cardswitch[aria-expanded="true"] { border-color: var(--line); background: var(--ground); }
.det-switch-lbl {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 600; color: var(--ink);
}
.det-switch-n {
  flex: none; font-size: 10.5px; font-weight: 700; font-variant-numeric: tabular-nums;
  background: var(--accent-soft); color: var(--accent); border-radius: 999px; padding: 1px 6px;
}
.det-switch-caret { flex: none; font-size: 9px; }
.crm-deal-menu {
  position: absolute; inset-block-start: 100%; inset-inline: 12px;
  z-index: 5; display: flex; flex-direction: column;
  background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 8px 24px -10px rgba(30, 43, 38, .35); overflow: hidden;
}
.crm-deal-menu[hidden] { display: none; }
.crm-deal-opt {
  display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
  border: none; background: none; cursor: pointer; text-align: start;
  padding: 8px 11px; border-block-end: 1px solid var(--line);
}
.crm-deal-opt:last-child { border-block-end: none; }
.crm-deal-opt:hover { background: var(--ground); }
.crm-deal-opt.on { background: var(--accent-soft); }
.crm-deal-opt-t {
  font-size: 12.5px; font-weight: 600; color: var(--ink);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.crm-deal-opt-m { font-size: 11.5px; color: var(--ink-soft); }
.crm-opt-stage { font-weight: 700; color: var(--st, var(--ink-soft)); }
.det-cardhead {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px;
  padding: 11px 16px; border: none; background: none; cursor: pointer;
  text-align: start; color: var(--ink);
}
.det-cardhead:hover { background: var(--ground); }
/* The empty card's call to action, in the header where it can be seen without
   expanding anything. */
.det-cardaction {
  flex: none; margin-inline: 0 16px; cursor: pointer;
  font-size: 11.5px; font-weight: 600; white-space: nowrap;
  padding: 4px 10px; border-radius: 6px;
  border: 1px solid var(--accent); background: var(--accent-soft); color: var(--accent);
}
.det-cardaction:hover { background: var(--accent); color: #fff; }
.det-cardaction:disabled { border-color: var(--line); background: var(--ground); color: var(--ink-soft); }
/* Beside a switcher there is no room for words — the label moves to title/aria. */
.det-cardaction.icon { padding: 4px 9px; font-size: 14px; line-height: 1; }
.det-caret { flex: none; font-size: 9px; color: var(--ink-soft); transition: transform .15s ease; }
.det-card.open .det-caret { transform: rotate(90deg); }
.det-ctitle { flex: none; font-size: 12.5px; font-weight: 700; }
/* The summary is the point of a collapsed card: it has to say enough that
   opening it is usually unnecessary. */
.det-csum {
  margin-inline-start: auto; text-align: end; min-width: 0;
  font-size: 12px; color: var(--ink-soft); line-height: 1.35; overflow-wrap: anywhere;
}
.det-csum .em { color: var(--ink); font-weight: 600; }
.det-csum .due { color: var(--warn); font-weight: 600; }
.det-csum .hot { color: var(--danger); font-weight: 600; }
.det-cardbody { display: none; padding: 0 16px 14px; }
.det-card.open .det-cardbody { display: block; }
.crm-addfu {
  margin-block-start: 8px; font-size: 12px; font-weight: 600; cursor: pointer;
  padding: 5px 11px; border-radius: 6px;
  border: 1px dashed var(--line); background: none; color: var(--accent);
}
.crm-addfu:hover { border-color: var(--accent); background: var(--accent-soft); }
@media (prefers-reduced-motion: reduce) { .det-caret { transition: none; } }
.det-tab:hover { color: var(--ink); }
.det-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.det-empty-props { font-size: 13px; color: var(--ink-soft); line-height: 1.6; padding: 8px 0; }
.cp-panel .cp-val-section:first-child .cp-val-sec-name { margin-top: 0; }

/* Custom properties — small screens */
@media (max-width: 640px) {
  /* Settings manager: let the section header and each property row wrap instead
     of overflowing a narrow settings body. */
  .cp-section-head { flex-wrap: wrap; }
  .cp-sec-name { flex: 1 1 100%; }
  .cp-prop { flex-wrap: wrap; gap: 4px 8px; }
  .cp-prop-name { flex: 1 1 100%; }
  .cp-prop-actions { margin-inline-start: auto; }
  /* Values: cascading dependent selects stack full-width so each is tappable. */
  .cp-dep { flex: 1 1 100%; min-width: 0; }
  .cp-field-ctl .cp-input, .cp-dep, .cp-multi { max-width: 100%; }
  /* Property modal: the tree editor never forces horizontal scroll. */
  .pm-node-row { gap: 4px; }
  .pm-node-row input { min-width: 0; }
  .pm-tree { max-height: 220px; }
  .det-tab { padding: 11px 6px; font-size: 13.5px; }
}
.label-chip {
  font-size: 11px; font-weight: 600; padding: 2px 9px; border-radius: 999px;
  color: #fff; border: none; opacity: 0.45;
}
.label-chip.on { opacity: 1; }
.assignee-select, .details select { width: 100%; padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; }
.ticket-mini {
  border: 1px solid var(--line); border-radius: 6px; padding: 8px 10px; margin-bottom: 6px; font-size: 12.5px;
  cursor: pointer; transition: border-color .12s ease, background .12s ease;
}
.ticket-mini:hover { border-color: var(--accent); background: var(--accent-soft); }
.ticket-mini .t-title { font-weight: 600; }
.ai-output {
  background: var(--ground); border: 1px solid var(--line); border-radius: 6px;
  padding: 10px; font-size: 12.5px; white-space: pre-wrap; line-height: 1.5; margin-top: 8px;
  max-height: 260px; overflow-y: auto;
}
.spin { display: inline-block; animation: spin 0.9s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------- generic page views ---------- */
.page { flex: 1; overflow-y: auto; padding: 22px 26px; }
.page h2 { margin: 0 0 4px; font-size: 20px; }
.page .page-sub { color: var(--ink-soft); margin-bottom: 18px; }

.page-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.page-head h2 { margin: 0; }

/* tickets: search + filter bar */
.ticket-filters {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin: 0 0 14px; padding: 10px 12px;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
}
.tf-search {
  flex: 1 1 220px; min-width: 160px; padding: 7px 10px; font-size: 13px;
  border: 1px solid var(--line); border-radius: 7px; background: var(--ground); color: var(--ink);
}
.tf-search:focus { outline: none; border-color: var(--accent); }
.tf-select {
  padding: 7px 10px; font-size: 13px; border: 1px solid var(--line); border-radius: 7px;
  background: var(--ground); color: var(--ink); cursor: pointer; max-width: 180px;
}
.tf-range {
  display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--ink-soft);
  border: 1px solid var(--line); border-radius: 7px; padding: 3px 8px; background: var(--ground);
}
.tf-range-lab { font-weight: 600; }
.tf-range input[type="date"] { border: none; background: none; color: var(--ink); font-size: 12.5px; padding: 2px; }
.tf-range input[type="date"]:focus { outline: none; }
.tf-dash { color: var(--ink-soft); }
/* label multi-select popover (native <details>) */
.tf-labels { position: relative; }
.tf-labels-btn {
  list-style: none; cursor: pointer; user-select: none;
  padding: 7px 10px; font-size: 13px; border: 1px solid var(--line); border-radius: 7px;
  background: var(--ground); color: var(--ink); white-space: nowrap;
}
.tf-labels-btn::-webkit-details-marker { display: none; }
.tf-labels[open] .tf-labels-btn { border-color: var(--accent); }
.tf-labels-pop {
  position: absolute; z-index: 40; top: calc(100% + 4px); inset-inline-start: 0; min-width: 200px; max-height: 260px;
  overflow-y: auto; background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 10px 28px rgba(20, 28, 25, 0.16); padding: 6px;
}
.tf-check { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; font-size: 13px; cursor: pointer; }
.tf-check:hover { background: var(--ground); }
.tf-check input { accent-color: var(--accent); }
.tf-check .color-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.tf-empty { padding: 8px; font-size: 12.5px; color: var(--ink-soft); }
.tf-clear:disabled { opacity: 0.5; cursor: default; }
.kanban-empty { color: var(--ink-soft); font-size: 12.5px; padding: 6px; }

/* tickets kanban */
.kanban { display: grid; grid-template-columns: repeat(3, minmax(240px, 1fr)); gap: 14px; align-items: start; }
.kanban-col { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 10px; }
.kanban-col h3 { margin: 2px 6px 10px; font-size: 13px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-soft); display: flex; justify-content: space-between; }
.tcard { border: 1px solid var(--line); border-radius: 6px; padding: 10px; margin-bottom: 8px; background: var(--ground); cursor: pointer; }
.tcard:hover { border-color: var(--accent); }
.tcard .t-num { font-size: 11px; color: var(--ink-soft); }
.tcard .t-title { font-weight: 600; margin: 2px 0 6px; }
.tcard-meta { display: flex; gap: 6px; align-items: center; font-size: 11px; color: var(--ink-soft); flex-wrap: wrap; }
.prio { font-weight: 700; font-size: 10.5px; padding: 1px 7px; border-radius: 4px; }
.prio.low { background: #eef2f1; color: var(--ink-soft); }
.prio.medium { background: #e4effc; color: var(--info); }
.prio.high { background: #fdf0e0; color: var(--warn); }
.prio.urgent { background: #fdeaea; color: var(--danger); }
.due-over { color: var(--danger); font-weight: 700; }
.tcard.active { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(15,122,84,0.18); }
.tcard-label { color: #fff; font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 4px; }

/* ticket detail slide-over (Jira-style) */
.tp-backdrop { position: fixed; inset: 0; background: rgba(20,28,25,0.32); z-index: 60; animation: tpFade 0.12s ease; }
@keyframes tpFade { from { opacity: 0; } to { opacity: 1; } }
.ticket-panel {
  position: fixed; top: 0; inset-inline-end: 0; height: 100vh; width: min(760px, 94vw);
  background: var(--panel); border-inline-start: 1px solid var(--line);
  box-shadow: -16px 0 40px rgba(20,28,25,0.16); z-index: 61;
  display: flex; flex-direction: column; animation: tpSlide 0.18s ease;
}
@keyframes tpSlide { from { transform: translateX(24px); opacity: 0.4; } to { transform: translateX(0); opacity: 1; } }
.tp-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid var(--line); }
.tp-head-id { font-weight: 700; font-size: 15px; color: var(--ink); }
.tp-close { border: none; background: none; font-size: 17px; cursor: pointer; color: var(--ink-soft); line-height: 1; padding: 4px 8px; border-radius: 6px; }
.tp-close:hover { background: var(--ground); color: var(--ink); }
.tp-loading, .tp-empty, .tp-readonly-note { color: var(--ink-soft); font-size: 13px; padding: 16px 18px; }
.tp-empty { padding: 14px 2px; }
.tp-body { flex: 1; display: grid; grid-template-columns: 1fr 260px; min-height: 0; }
.tp-main { padding: 18px; overflow-y: auto; display: flex; flex-direction: column; min-width: 0; }
.tp-side { border-inline-start: 1px solid var(--line); padding: 18px 16px; overflow-y: auto; background: var(--ground); }
.tp-title { font-size: 18px; font-weight: 700; margin: 0 0 6px; color: var(--ink); }
.tp-chat { font-size: 12.5px; color: var(--ink-soft); margin-bottom: 14px; }
.tp-chat a { color: var(--accent); text-decoration: none; }
.tp-chat a:hover { text-decoration: underline; }
.tp-context { border: 1px solid var(--line); border-radius: 10px; background: var(--ground); padding: 10px 12px; margin-bottom: 16px; }
.tp-context-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.tp-cust { font-weight: 700; font-size: 13px; color: var(--ink); }
.tp-cust-num { font-size: 12px; color: var(--ink-soft); }
.tp-context-label { margin-inline-start: auto; font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-soft); }
.tp-thread { display: flex; flex-direction: column; gap: 6px; max-height: 260px; overflow-y: auto; }
.tp-msg { max-width: 82%; padding: 6px 9px; border-radius: 9px; font-size: 12.5px; border: 1px solid var(--line); background: var(--panel); }
.tp-msg.in { align-self: flex-start; border-end-start-radius: 2px; }
.tp-msg.out { align-self: flex-end; background: #e7f5ee; border-color: #cdeadd; border-end-end-radius: 2px; }
.tp-msg.note { align-self: center; max-width: 92%; background: #fff8e6; border-color: #f2e2b6; }
.tp-msg.src { box-shadow: 0 0 0 2px rgba(15,122,84,0.28); border-color: var(--accent); }
.tp-msg-who { font-size: 10.5px; font-weight: 700; color: var(--ink-soft); margin-bottom: 2px; display: flex; align-items: center; gap: 6px; }
.tp-src-tag { font-weight: 700; font-size: 9px; text-transform: uppercase; letter-spacing: 0.04em; color: #fff; background: var(--accent); border-radius: 4px; padding: 1px 5px; }
.tp-msg-body { color: var(--ink); white-space: pre-wrap; word-break: break-word; }
.tp-msg-media { display: inline-block; font-size: 11px; color: var(--ink-soft); margin-inline-end: 5px; }
.tp-msg-time { font-size: 10px; color: var(--ink-soft); margin-top: 3px; text-align: end; }
.tp-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: 12px; }
.tp-tab { border: none; background: none; padding: 8px 12px; font-size: 13px; font-weight: 600; color: var(--ink-soft); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }
.tp-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.tp-tab span { background: var(--ground); border-radius: 10px; padding: 0 6px; font-size: 11px; margin-inline-start: 2px; }
.tp-comments { display: flex; flex-direction: column; gap: 12px; }
.tp-comment { border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; background: var(--panel); }
.tp-comment-head { display: flex; align-items: center; gap: 8px; font-size: 12.5px; margin-bottom: 5px; }
.tp-avatar { width: 22px; height: 22px; border-radius: 50%; background: var(--accent); color: #fff; display: inline-flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; }
.tp-time { color: var(--ink-soft); font-size: 11px; }
.tp-comment-body { font-size: 13.5px; color: var(--ink); white-space: pre-wrap; word-break: break-word; }
.tp-compose { margin-top: 14px; display: flex; flex-direction: column; gap: 8px; }
.tp-compose textarea { width: 100%; border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; font: inherit; resize: vertical; }
.tp-compose .btn { align-self: flex-end; }
.tp-history { display: flex; flex-direction: column; gap: 2px; }
.tp-event { display: flex; gap: 10px; padding: 8px 0; align-items: flex-start; }
.tp-event-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--line); margin-top: 5px; flex: none; box-shadow: 0 0 0 3px var(--ground); }
.tp-event-text { font-size: 13px; color: var(--ink); }
.tp-event-text b { font-weight: 700; }
.hist-label { display: inline-block; color: #fff; font-size: 11px; font-weight: 700; padding: 0 6px; border-radius: 4px; vertical-align: baseline; }
.hist-field { color: var(--accent); font-weight: 700; }
.tp-field { margin-bottom: 14px; }
.tp-field label { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-soft); margin-bottom: 5px; }
.tp-select { width: 100%; padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); font: inherit; }
.tp-static { font-size: 13px; color: var(--ink); }
.tp-due-warn { display: inline-block; margin-top: 5px; font-size: 11.5px; font-weight: 700; color: var(--danger); }
.tp-reporter { display: flex; align-items: center; gap: 8px; }
.tp-avatar.bot { background: var(--ink-soft); }
.tp-via { color: var(--ink-soft); font-size: 11px; }
.tp-field-hint { font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--ink-soft); font-size: 10px; }
.tp-label-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.tp-label-checks { max-height: 130px; }
.tp-muted { color: var(--ink-soft); font-weight: 400; }
.form-hint { font-weight: 400; color: var(--ink-soft); font-size: 11px; }
@media (max-width: 640px) {
  .tp-body { grid-template-columns: 1fr; }
  .tp-side { border-inline-start: none; border-top: 1px solid var(--line); }
}

/* rules */
.rule-card {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px 16px; margin-bottom: 12px; display: flex; gap: 14px; align-items: flex-start;
}
.rule-card .r-body { flex: 1; }
.rule-card .r-name { font-weight: 700; margin-bottom: 4px; }
.rule-kind {
  font-size: 10.5px; font-weight: 600; color: var(--info); background: #e4effc;
  border-radius: 4px; padding: 1px 7px; margin-inline-start: 6px; text-transform: none;
}
.rule-flow { font-size: 12.5px; color: var(--ink-soft); line-height: 1.6; }
.rule-flow b { color: var(--ink); }
.rule-runs { font-size: 12px; color: var(--accent); font-weight: 600; margin-top: 6px; }
.switch { position: relative; width: 40px; height: 22px; flex: 0 0 40px; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider {
  position: absolute; inset: 0; background: #cfd8d3; border-radius: 999px; transition: 0.2s;
}
.slider:before {
  content: ""; position: absolute; height: 16px; width: 16px; inset-inline-start: 3px; top: 3px;
  background: #fff; border-radius: 50%; transition: 0.2s;
}
.switch input:checked + .slider { background: var(--accent); }
.switch input:checked + .slider:before { transform: translateX(18px); }

/* cards */
.card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px; }

/* Settings → Data */
.data-block { margin-top: 22px; }
.data-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 0; }
.data-row + .data-row { border-top: 1px solid var(--line); }
.data-row .btn { flex: 0 0 auto; }
.data-hint { font-size: 12.5px; color: var(--ink-soft); margin-top: 3px; max-width: 54ch; }
.danger-zone { border-color: color-mix(in srgb, var(--danger) 40%, var(--line)); }
.dz-head { font-weight: 700; color: var(--danger); font-size: 12px; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 6px; }
.dz-form { margin-top: 12px; }
.dz-form input { min-width: 240px; }
@media (max-width: 640px) {
  .data-row { flex-direction: column; align-items: stretch; gap: 10px; }
  .data-row .btn { align-self: flex-start; }
  .dz-form { align-items: stretch; }
  .dz-form input { min-width: 0; flex: 1 1 100%; }
  .dz-form .btn { width: 100%; }
}
.card h3 { margin: 0 0 10px; font-size: 15px; }

/* dashboard */
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin-bottom: 20px; }
.stat {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px;
}
.stat .s-val { font-size: 26px; font-weight: 800; font-variant-numeric: tabular-nums; }
.stat .s-label { font-size: 12px; color: var(--ink-soft); margin-top: 2px; }
.stat.alert .s-val { color: var(--danger); }
.dash-grid { display: grid; grid-template-columns: 3fr 2fr; gap: 16px; align-items: start; }
table.agents { width: 100%; border-collapse: collapse; font-size: 13px; }
table.agents th { text-align: start; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-soft); padding: 6px 8px; border-bottom: 1px solid var(--line); }
table.agents td { padding: 8px; border-bottom: 1px solid var(--ground); font-variant-numeric: tabular-nums; }
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-inline-end: 6px; }
.dot.online { background: var(--accent); }
.dot.offline { background: #cfd8d3; }
.bars { display: flex; align-items: flex-end; gap: 3px; height: 120px; padding-top: 10px; }
.bar { flex: 1; background: var(--accent-soft); border-radius: 3px 3px 0 0; position: relative; min-height: 2px; }
.bar.hot { background: var(--accent); }
.bar-x { font-size: 9px; color: var(--ink-soft); text-align: center; margin-top: 3px; }

/* ticket aging (dashboard) */
.aging { display: flex; flex-direction: column; gap: 8px; }
.aging-row { display: flex; align-items: center; gap: 10px; font-size: 12.5px; }
.aging-k { flex: 0 0 46px; font-weight: 600; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.aging-track { flex: 1; height: 10px; background: var(--ground); border-radius: 999px; overflow: hidden; }
.aging-fill { height: 100%; background: var(--accent); border-radius: 999px; min-width: 2px; }
.aging-fill.hot { background: var(--danger); }
.aging-v { flex: 0 0 26px; text-align: end; font-weight: 700; font-variant-numeric: tabular-nums; }

/* ---------- settings ---------- */
.settings { flex: 1; display: flex; min-width: 0; }
.settings-nav {
  flex: 0 0 240px; background: var(--panel); border-inline-end: 1px solid var(--line);
  padding: 18px 12px; display: flex; flex-direction: column; gap: 4px;
  /* Fifteen tabs and a plan card do not fit a short window, and without this
     the card at the bottom -- the one carrying Change plan -- was simply
     unreachable. min-height: 0 because a flex child will not shrink below its
     content otherwise, and overflow-y then has nothing to scroll. */
  min-height: 0; overflow-y: auto;
}
.settings-nav h3 { margin: 0 6px 10px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--ink-soft); }
.settings-tab {
  text-align: start; border: none; background: none; border-radius: 8px;
  padding: 8px 10px; font-size: 13.5px; font-weight: 600; color: var(--ink-soft);
}
.settings-tab:hover { background: var(--ground); }
.settings-tab.active { background: var(--accent-soft); color: var(--accent); }
.settings-plan { margin-top: auto; border-top: 1px solid var(--line); padding: 12px 6px 2px; display: flex; flex-direction: column; gap: 8px; }
.settings-plan-name { font-size: 12.5px; font-weight: 700; color: var(--ink-soft); }
.settings-body { flex: 1; overflow-y: auto; padding: 22px 26px; min-width: 0; }
.settings-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 4px; }
.settings-head h2 { margin: 0; font-size: 20px; }

/* phones */
.phone-list { display: flex; flex-direction: column; gap: 10px; max-width: 720px; }
.phone-row {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px 14px;
}
.phone-ico { font-size: 22px; }
/* A real minimum, not flex-basis 0: the action row carries up to six buttons,
   and with basis 0 they crushed this column to ~90px — the last-synced line
   rendered one word per line under the status chip. Wrap the row instead. */
.phone-info { flex: 1 1 220px; min-width: 180px; }
.phone-name { font-weight: 700; }
.phone-num { font-size: 12.5px; color: var(--ink-soft); }
/* Last synced. Quiet while it is fresh; a warning past an hour, because a stale
   number has to be readable as a fault WITHOUT reading the duration. */
.phone-synced { font-size: 12px; color: var(--ink-soft); margin-top: 2px; }
.phone-synced.stale { color: var(--danger); font-weight: 650; }
.phone-synced.stale::before { content: '⚠ '; }
/* History parked behind the privacy review — everything synced, waiting on the
   owner. Amber, not red: it is a decision pending, not a fault. */
.phone-synced.holding { color: var(--warn); font-weight: 600; }
.phone-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.pstatus { font-size: 12px; font-weight: 700; padding: 3px 10px; border-radius: 999px; white-space: nowrap; }
.pstatus.ok { background: var(--accent-soft); color: var(--accent); }
.pstatus.warn { background: #fdf0e0; color: var(--warn); }
.pstatus.off { background: var(--ground); color: var(--ink-soft); }
.pstatus.err { background: #fdeaea; color: var(--danger); }

/* QR panel */
.qr-box {
  display: grid; place-items: center; min-height: 260px; margin: 6px 0 10px;
  background: var(--ground); border: 1px dashed var(--line); border-radius: 10px;
}
.qr-box img { width: 240px; height: 240px; image-rendering: pixelated; border-radius: 6px; background: #fff; padding: 8px; }
.qr-wait { color: var(--ink-soft); font-size: 13px; }
.qr-done { font-size: 20px; font-weight: 800; color: var(--accent); }
.qr-status { font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; }

/* team */
.team-table { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.team-table td, .team-table th { padding: 9px 10px; }
.role-tag { font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 999px; background: var(--ground); color: var(--ink-soft); }
.role-tag.owner { background: var(--accent-soft); color: var(--accent); }
.role-tag.admin { background: #e4effc; color: var(--info); }
.role-tag.readonly { background: #fdf0e0; color: var(--warn); }
/* Edit + Remove sit together in the last column and stay on one line; the mobile
   card layout below re-anchors them to the end of the card. */
.row-actions { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: wrap; }

/* Remove-teammate dialog: the warning carries the irreversible part, the note
   below the picker restates where the work actually lands. */
.rm-warn {
  border: 1px solid color-mix(in srgb, var(--danger) 35%, var(--line));
  background: color-mix(in srgb, var(--danger) 6%, var(--panel));
  border-radius: 8px; padding: 10px 12px; margin-block-end: 14px;
  font-size: 13px; line-height: 1.5; color: var(--ink);
}
.rm-note { font-size: 12.5px; line-height: 1.5; color: var(--ink-soft); margin-block-start: -4px; }
.label-checks { display: flex; flex-direction: column; gap: 4px; max-height: 160px; overflow-y: auto; border: 1px solid var(--line); border-radius: 6px; padding: 8px; }
.check-line { display: flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 400 !important; color: var(--ink) !important; text-transform: none !important; }
.color-dot { display: inline-block; width: 11px; height: 11px; border-radius: 50%; flex: 0 0 11px; }
.color-dot.big { width: 16px; height: 16px; flex: 0 0 16px; }

/* labels + quick replies editors */
.inline-form {
  display: flex; align-items: center; gap: 8px; margin-bottom: 14px; max-width: 720px;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 10px 12px;
}
.inline-form input[type="text"] { padding: 7px 10px; border: 1px solid var(--line); border-radius: 6px; }
.inline-form input[type="color"] { width: 34px; height: 30px; padding: 2px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); }
.label-list, .qr-list { display: flex; flex-direction: column; gap: 8px; max-width: 720px; }
.qr-section { font-size: 13px; font-weight: 600; color: var(--ink-soft); margin: 18px 0 8px; text-transform: uppercase; letter-spacing: .04em; }
.label-row, .qr-row {
  display: flex; align-items: center; gap: 8px;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 8px 12px;
}
.label-row input[type="text"], .qr-row input[type="text"] { padding: 6px 9px; border: 1px solid var(--line); border-radius: 6px; }
.label-row input[type="color"] { width: 30px; height: 28px; padding: 2px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); }
/* ---- auto-labelling config, inside a label row ---- */
/* The card wraps the row so the configuration can sit UNDER the label it
   belongs to. Shown only when the switch is on: an empty hint box beside every
   label would read as work somebody has not done. */
.label-card { border: 1px solid var(--line); border-radius: 10px; background: var(--panel); padding: 8px 10px; }
.label-card .label-row { border: none; background: none; padding: 0; }
.lb-auto { display: flex; align-items: center; gap: 5px; font-size: 12.5px; color: var(--ink-soft); white-space: nowrap; }
.lb-tag { font-size: 11px; padding: 1px 7px; border-radius: 999px; border: 1px solid var(--line); color: var(--ink-soft); }
.lb-auto-cfg { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--line); }
.lb-auto-cfg input[type="text"] { padding: 6px 9px; border: 1px solid var(--line); border-radius: 6px; font-size: 13px; }
/* Clay, not the accent: this is a warning about who can read a conversation,
   not a feature being promoted. */
.lb-warn { font-size: 12px; line-height: 1.45; color: #8a6a12; background: #fdf6e3; border: 1px solid #e8d9a8; border-radius: 8px; padding: 7px 9px; }
.label-name-input { flex: 1; }
.slash-prefix { font-weight: 800; color: var(--accent); }
.qr-cmd-input { width: 130px; }
.qr-body-input { flex: 1; min-width: 0; }

/* ---------- upgrade modal / plan picker ---------- */
.plan-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.plan-card {
  border: 1px solid var(--line); border-radius: 10px; padding: 14px 12px;
  display: flex; flex-direction: column; gap: 6px; background: var(--ground);
}
.plan-card.featured { border-color: var(--accent); background: var(--accent-soft); }
.plan-card.current { outline: 2px solid var(--accent); outline-offset: -2px; }
.plan-name { font-weight: 800; font-size: 15px; }
.plan-phones { font-size: 12px; color: var(--ink-soft); font-weight: 600; }
.plan-feats { list-style: none; margin: 4px 0 8px; padding: 0; font-size: 11.5px; color: var(--ink-soft); line-height: 1.7; flex: 1; }
.plan-card .btn { width: 100%; }

/* context menu + modal */
.ctx-menu {
  position: fixed; z-index: 80; background: var(--panel); border: 1px solid var(--line);
  border-radius: 8px; box-shadow: 0 8px 24px rgba(30, 43, 38, 0.15); padding: 4px; min-width: 180px;
}
.ctx-menu button { display: flex; align-items: center; gap: 7px; width: 100%; text-align: start; background: none; border: none; padding: 8px 12px; border-radius: 5px; font-size: 13px; }
.ctx-menu button:hover { background: var(--ground); }
.ctx-reacts { display: flex; gap: 2px; padding: 4px 4px 6px; border-bottom: 1px solid var(--line, #eee); margin-bottom: 4px; }
.ctx-menu button.ctx-react { width: auto; justify-content: center; padding: 4px 6px; font-size: 18px; border-radius: 8px; }
.ctx-menu button.ctx-react.on { background: var(--ground); box-shadow: inset 0 0 0 1.5px var(--brand, #25d366); }
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(20, 30, 26, 0.4); z-index: 90;
  display: grid; place-items: center;
}
.modal {
  background: var(--panel); border-radius: 12px; padding: 20px 22px; width: 480px; max-width: 94vw;
  max-height: 90vh; overflow-y: auto;
  box-shadow: 0 20px 50px rgba(20, 30, 26, 0.3);
}
.modal h3 { margin: 0 0 14px; }
.modal .form-row, .form-row { margin-bottom: 12px; }
.modal label, .form-row label { display: block; font-size: 12px; font-weight: 600; color: var(--ink-soft); margin-bottom: 4px; }
.modal input[type="text"], .modal input[type="email"], .modal input[type="password"], .modal select, .modal input[type="datetime-local"] {
  width: 100%; padding: 8px 10px; border: 1px solid var(--line); border-radius: 6px;
}
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
.btn { padding: 8px 16px; border-radius: 6px; border: 1px solid var(--line); background: var(--panel); font-weight: 600; }
.btn.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.btn.sm { padding: 4px 10px; font-size: 12px; border-radius: 6px; }
.btn.xs { padding: 2px 8px; font-size: 11.5px; border-radius: 5px; }
.btn.danger { color: var(--danger); border-color: #f3c8c8; }
.btn.danger:hover { background: #fdeaea; }
.btn:hover { filter: brightness(0.98); }

/* Settings: Security / two-factor */
.sec-card {
  max-width: 560px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel); padding: 18px; margin-top: 12px;
}
.sec-card-title { font-weight: 700; font-size: 15px; margin-bottom: 4px; }
.sec-desc { font-size: 13px; color: var(--ink-soft); line-height: 1.5; margin: 0 0 12px; }
.twofa-state { font-size: 14px; margin-bottom: 12px; }
.twofa-state.on { color: var(--accent); }
.twofa-steps { font-size: 13.5px; color: var(--ink); margin: 0 0 12px; padding-inline-start: 20px; line-height: 1.7; }
.twofa-qr { display: flex; justify-content: center; padding: 8px; background: #fff; border: 1px solid var(--line); border-radius: 8px; width: 216px; }
.twofa-qr img { display: block; }
.twofa-key { font-size: 12.5px; color: var(--ink-soft); margin: 12px 0; line-height: 1.6; }
.twofa-key code {
  display: inline-block; margin-top: 4px; font-size: 13px; letter-spacing: 0.08em;
  background: var(--ground); border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px; user-select: all;
}
.twofa-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 4px; }
/* Grows rather than sitting at a fixed 150px: the re-enrolment field's
   placeholder is "6-digit code or XXXXX-XXXXX", which a 150px box truncates to
   nonsense on the exact screen where the instruction matters most. */
.twofa-row input {
  flex: 1 1 190px; min-width: 0; max-width: 260px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 6px;
  background: var(--ground); font-size: 15px; letter-spacing: 0.08em; text-align: center;
}
/* Recovery codes. Monospace and generously leaded because these get read off a
   screen and typed back by hand, often from a photo, under time pressure. */
.twofa-codes {
  margin: 8px 0 0; padding: 10px 12px; background: var(--ground); border: 1px solid var(--line);
  border-radius: 8px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13.5px; line-height: 1.9; letter-spacing: 0.06em; text-align: center;
  user-select: all; white-space: pre; overflow-x: auto;
}
.twofa-low { color: var(--danger, #c4553c); }

/* The ⓘ on the scan screen, and the dialog behind it.
   A dialog rather than a panel that unfolds in place: this sits between the key
   and the code field, and anything that expands there pushes the field somebody
   is mid-way through using off a short screen. */
.twofa-help-btn {
  display: block; width: 100%; margin: 14px 0 12px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 9px; background: var(--ground);
  font: inherit; font-size: 13.5px; font-weight: 600; color: var(--accent);
  text-align: start; cursor: pointer;
}
.twofa-help-btn:hover { background: var(--accent-soft); }
.twofa-help-dialog { background: var(--panel); color: var(--ink); box-shadow: 0 10px 40px rgba(0,0,0,.18); }
.twofa-help-dialog::backdrop { background: rgba(20,32,28,.45); }
.twofa-help-head {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 15px 16px 12px; border-bottom: 1px solid var(--line);
}
.twofa-help-head h3 { flex: 1 1 auto; margin: 0; font-size: 15.5px; }
.twofa-help-x {
  flex: none; width: 30px; height: 30px; margin: -4px -4px 0 0; padding: 0;
  border: 0; border-radius: 8px; background: none; color: var(--ink-soft);
  font: inherit; font-size: 14px; cursor: pointer;
}
.twofa-help-x:hover { background: var(--ground); color: var(--ink); }
.twofa-help-foot { padding: 12px 16px 14px; border-top: 1px solid var(--line); text-align: end; }
.twofa-help-ok {
  min-height: 40px; padding: 8px 18px; border: 0; border-radius: 9px;
  background: var(--accent); color: #fff; font: inherit; font-weight: 600; cursor: pointer;
}
/* Capped and scrollable so a long answer never pushes its own Got it button off
   a landscape phone — the screen this is most likely to be read on. */
.twofa-help-body {
  padding: 13px 16px; font-size: 13px; line-height: 1.65; color: var(--ink-soft);
  max-height: min(60vh, 460px); overflow-y: auto;
}
.twofa-help-p { margin: 0 0 9px; }
.twofa-help-p:last-child { margin-bottom: 0; }
.twofa-apps, .twofa-manual { margin: 0 0 10px; padding-inline-start: 19px; }
.twofa-apps li, .twofa-manual li { margin-bottom: 5px; }
.twofa-apps li:last-child, .twofa-manual li:last-child { margin-bottom: 0; }
.twofa-help-body b { color: var(--ink); font-weight: 620; }
.twofa-qr img { max-width: 100%; height: auto; }

@media (max-width: 640px) {
  /* 16px is the threshold below which iOS Safari zooms the page on focus. On a
     one-shot code field that zoom is the difference between typing a code and
     fighting the viewport with a 30-second clock running. */
  .twofa-row input { flex-basis: 100%; max-width: none; font-size: 16px; }
  /* Full-width targets, and a real 44px tap area — these are the buttons someone
     presses while locked out of their own workspace. */
  .twofa-row .btn { flex: 1 1 100%; min-height: 44px; }
  /* The QR box is a fixed 216px; let it shrink on a 320px screen with padding. */
  .twofa-qr { width: auto; max-width: 216px; }
  .twofa-codes { font-size: 13px; letter-spacing: 0.04em; }
  .twofa-help-ok { width: 100%; min-height: 44px; }
}

/* ---------- auth (login/signup) page ---------- */
.auth-body { overflow: auto; background: var(--ground); }

/* THE SPLIT.
 *
 * Two columns: a decorative scene, and the card that has always been here. The
 * scene is FIRST IN THE MARKUP and there is deliberately no [dir="rtl"] rule
 * for it anywhere — grid columns are laid out in the writing direction, so
 * Arabic mirrors the whole page for free.
 *
 * minmax(0, …) on both tracks, not a bare fr: an fr track's minimum is auto,
 * so a long unbroken string in a form field would widen the column instead of
 * wrapping, and the two halves would drift apart as somebody typed. */
.auth-split {
  min-height: 100vh; min-height: 100dvh;
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
}

/* A SCENE, NOT A PHOTOGRAPH. Rounded rectangles standing in for a conversation:
 * no image files, so this costs the sign-in page nothing over the wire, and no
 * words, so there is nothing here for the five dictionaries to carry. */
.auth-scene {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 34px; padding: 40px;
  background:
    radial-gradient(120% 90% at 12% 8%, rgba(31, 176, 122, 0.55) 0%, rgba(31, 176, 122, 0) 55%),
    radial-gradient(90% 80% at 88% 22%, rgba(12, 122, 190, 0.38) 0%, rgba(12, 122, 190, 0) 60%),
    radial-gradient(110% 100% at 70% 100%, rgba(9, 90, 62, 0.75) 0%, rgba(9, 90, 62, 0) 60%),
    linear-gradient(155deg, #0d3f2d 0%, #0a2e21 60%, #071e16 100%);
}
/* --flip is the token [dir="rtl"] already sets to -1 for glyphs that encode a
   direction. A tilt is one of those: lit from the outer edge in English, it has
   to be lit from the other edge once the panel moves across. */
/* NO 3D TRANSFORM. It was tried at 9deg and again at 16deg and neither earned
   its cost: a rotated element is rasterised onto its own compositing layer and
   then texture-mapped, which softens the text inside it — and the text inside
   this one is the smallest on the page, down to 8.5px on the timestamps and the
   ticket chips. Depth here comes from the shadow instead, which costs the
   rendering nothing. */
.scene-stage { width: min(468px, 100%); }
/* THE APP, IN MINIATURE.
 *
 * Not a chat card on a gradient — the product's own shell: the 54px icon rail,
 * the chat list, the conversation. Every colour below is a token this
 * stylesheet already defines for the real thing (--panel, --ground, --line,
 * --accent-soft) or the literal the real bubble uses (#dcf3e8 / #c4e6d6), so
 * this stays honest for free: restyle the inbox and the sign-in page follows.
 *
 * Light, because the product is light. A dark glass version of this read as
 * generic SaaS and told a visitor nothing about what they were signing in to. */
.scene-card {
  display: flex; flex-direction: column; overflow: hidden;
  border-radius: 12px; background: var(--panel); border: 1px solid var(--line);
  /* Three layers doing the job the tilt used to. A tight contact shadow, a
     mid one for body, and a wide soft one for the lift off the gradient —
     which is all the depth was ever for, and this way nothing is rasterised. */
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.16), 0 12px 28px rgba(0, 0, 0, 0.22), 0 40px 80px rgba(0, 0, 0, 0.30);
  font-size: 11px; color: var(--ink); text-align: start;
}
.sw-top {
  flex: none; height: 26px; display: flex; align-items: center; gap: 6px;
  padding-inline-start: 12px; border-bottom: 1px solid var(--line);
}
.sw-top i { width: 8px; height: 8px; border-radius: 50%; }
.sw-top i:nth-child(1) { background: #f0a9a5; }
.sw-top i:nth-child(2) { background: #f3d29a; }
.sw-top i:nth-child(3) { background: #a9dcbd; }
.sw-body { display: flex; height: 258px; min-height: 0; }
/* Three views in one box. Absolutely positioned so they occupy the same space
   and cross-fade rather than reflowing the window around them — a frame that
   changed size between Mail and Tickets would look like a bug, not a tour. */
.sw-views { position: relative; flex: 1; min-width: 0; }
.sv { position: absolute; inset: 0; display: flex; min-width: 0; }

/* The rail, at the real thing's proportions: 40px buttons in 54px become 28 in 38. */
.sw-rail {
  flex: none; width: 38px; display: flex; flex-direction: column; align-items: center;
  gap: 4px; padding-top: 8px; border-inline-end: 1px solid var(--line);
}
.sw-rb { width: 28px; height: 28px; border-radius: 8px; display: grid; place-items: center; font-size: 13px; }
.sw-rb.active { background: var(--accent-soft); }

.sw-list { flex: none; width: 148px; overflow: hidden; border-inline-end: 1px solid var(--line); }
.sw-row { display: flex; align-items: center; gap: 7px; padding: 8px 9px; border-bottom: 1px solid var(--ground); }
.sw-row.active { background: var(--accent-soft); }
.sw-av {
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; background: var(--accent); color: #fff;
}
.sw-av svg { width: 17px; height: 17px; }
.sw-av2 { background: #6b8f7f; }
.sw-rowbody { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.sw-rowtop { display: flex; align-items: baseline; gap: 5px; }
.sw-rowtop b { font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sw-rowtop em { margin-inline-start: auto; flex: none; font-style: normal; font-size: 9px; color: var(--ink-soft); }
.sw-prev { font-size: 10px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sw-unread {
  flex: none; background: var(--accent); color: #fff; font-size: 9px; font-weight: 700;
  border-radius: 999px; padding: 1px 6px;
}

.sw-conv { flex: 1; min-width: 0; display: flex; flex-direction: column; background: var(--ground); }
.sw-convhead {
  flex: none; display: flex; align-items: center; gap: 8px; padding: 7px 10px;
  background: var(--panel); border-bottom: 1px solid var(--line);
}
.sw-convhead > b { font-size: 11px; font-weight: 700; }
.sw-assign {
  margin-inline-start: auto; display: inline-flex; align-items: center; gap: 4px;
  font-size: 9px; color: var(--ink-soft);
  background: var(--ground); border: 1px solid var(--line); border-radius: 999px; padding: 2px 7px;
}
.sw-assign b { color: var(--ink); font-weight: 700; }
.sw-adot { flex: none; width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }

.sw-thread { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 6px; padding: 10px; }
/* max-width rather than width: a bubble is as wide as what somebody typed, and
   these sentences are five different lengths in five languages. */
.sw-msg {
  max-width: 84%; padding: 6px 9px; border-radius: 10px; line-height: 1.4;
  display: flex; flex-direction: column; gap: 1px;
  background: var(--panel); border: 1px solid var(--line);
}
.sw-msg.in { margin-inline-end: auto; }
.sw-msg.out { margin-inline-start: auto; background: #dcf3e8; border-color: #c4e6d6; }
.sw-msg time { align-self: flex-end; display: inline-flex; align-items: center; gap: 3px; font-size: 8.5px; color: var(--ink-soft); }
.sc-ticks { width: 15px; height: 9px; }

/* ---- Mail ---- */
.sw-mrow { align-items: flex-start; }
.sw-reader { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; padding: 10px; background: var(--ground); }
.sw-rsubj { font-size: 11px; font-weight: 700; }
.sw-rbody { font-size: 10px; line-height: 1.5; color: var(--ink-soft); }

/* ---- Tickets ---- */
.sw-tickets { flex: 1; min-width: 0; display: flex; flex-direction: column; background: var(--ground); }
.sw-trow {
  display: flex; align-items: center; gap: 8px; padding: 10px 12px;
  background: var(--panel); border-bottom: 1px solid var(--line);
}
.sw-tid { flex: none; font-size: 9px; font-weight: 700; color: var(--ink-soft); }
.sw-ttitle { flex: 1; min-width: 0; font-size: 10.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sw-tst { flex: none; font-size: 8.5px; font-weight: 700; border-radius: 999px; padding: 2px 7px; }
.sw-tst.open { background: var(--accent-soft); color: var(--accent); }
.sw-tst.wait { background: var(--note); color: #8a6d1f; }
.sw-tst.done { background: var(--ground); color: var(--ink-soft); border: 1px solid var(--line); }

/* Them typing back, aligned to the side their messages come from. */
.sc-typing {
  align-self: flex-start; display: flex; align-items: center; gap: 3px;
  padding: 8px 10px; border-radius: 10px;
  background: var(--panel); border: 1px solid var(--line);
}
.sc-typing span { width: 5px; height: 5px; border-radius: 50%; background: var(--ink-soft); }

/* ---- the loop -----------------------------------------------------------
 *
 * A conversation assembling itself: they write, we answer, the ticks turn
 * read, they write back, the thread lands on somebody's plate.
 *
 * ONE DURATION, AND THE STAGGER LIVES IN THE KEYFRAME PERCENTAGES rather than
 * in animation-delay. Delays would phase-shift each element permanently, so
 * after the first pass a bubble would be arriving while the chip from the
 * previous conversation was still on screen. Every element here starts and
 * clears together, which is what makes it read as one conversation repeating
 * instead of five things blinking.
 *
 * No opacity:0 in any of the rules above — only inside the keyframes. That is
 * what lets the reduced-motion block at the end simply switch the animations
 * off and land on the finished composition. */
.auth-scene {
  --sc-loop: 24s;
  /* The product's own receipt colours, used as-is now that the bubble under
     them is the product's own light one. They were lightened while this panel
     was dark glass; against #dcf3e8 the real values are simply correct. */
  --sc-tick-read: var(--tick-read);
  --sc-tick-sent: var(--tick-grey);
}
/* ---- the tour ----------------------------------------------------------
 *
 * 24 seconds, three views. Inbox 0-36%, Mail 40-66%, Tickets 70-96%, then a
 * beat of nothing before it comes round again.
 *
 * ONLY THE INBOX IS CHOREOGRAPHED LINE BY LINE. A conversation arrives one
 * message at a time and that is the thing worth showing; Mail and Tickets are
 * screens somebody switched to, so they cross-fade in whole. Animating every
 * row of all three would be motion for its own sake on a sign-in page.
 *
 * The window itself never moves. Animating the frame would be a screenshot
 * wobbling; animating what appears inside it is the product working.
 *
 * Longhand rather than the shorthand, because the shorthand resets
 * animation-name and would wipe every line under it. */
.sv, .sw-rb1, .sw-rb2, .sw-rb3,
.sw-msg, .sc-typing, .sw-unread {
  animation-duration: var(--sc-loop); animation-timing-function: ease-in-out;
  animation-iteration-count: infinite; animation-fill-mode: backwards;
}
.sv-inbox   { animation-name: sv-inbox; }
.sv-mail    { animation-name: sv-mail; }
.sv-tickets { animation-name: sv-tickets; }
/* The rail lights whichever view is up, on the same window as the view itself,
   so the icon and the screen can never disagree. */
.sw-rb1 { animation-name: sv-inbox-lit; }
.sw-rb2 { animation-name: sv-mail-lit; }
.sw-rb3 { animation-name: sv-tickets-lit; }
.sw-m1 { animation-name: sc-m1; }
.sw-m2 { animation-name: sc-m2; }
.sw-m3 { animation-name: sc-m3; }
.sw-m4 { animation-name: sc-m4; }
.sc-typing { animation-name: sc-type; }
.sw-unread { animation-name: sc-unread; }
.sc-ticks  { animation: sc-read var(--sc-loop) ease-in-out infinite backwards; }
.sc-typing span { animation: sc-dot 1.1s ease-in-out infinite; }
.sc-typing span:nth-child(2) { animation-delay: 0.15s; }
.sc-typing span:nth-child(3) { animation-delay: 0.30s; }

@keyframes sv-inbox   { 0%, 1%  { opacity: 0; } 3%, 34%  { opacity: 1; } 37%, 100% { opacity: 0; } }
@keyframes sv-mail    { 0%, 39% { opacity: 0; transform: translateY(8px); } 42%, 64% { opacity: 1; transform: none; } 67%, 100% { opacity: 0; transform: none; } }
@keyframes sv-tickets { 0%, 69% { opacity: 0; transform: translateY(8px); } 72%, 94% { opacity: 1; transform: none; } 97%, 100% { opacity: 0; transform: none; } }
@keyframes sv-inbox-lit   { 0%, 1%  { background: transparent; } 3%, 34% { background: var(--accent-soft); } 37%, 100% { background: transparent; } }
@keyframes sv-mail-lit    { 0%, 39% { background: transparent; } 42%, 64% { background: var(--accent-soft); } 67%, 100% { background: transparent; } }
@keyframes sv-tickets-lit { 0%, 69% { background: transparent; } 72%, 94% { background: var(--accent-soft); } 97%, 100% { background: transparent; } }

/* Four messages inside the inbox window, each on its own beat, all clearing
   together when the view hands over to Mail. */
@keyframes sc-m1 { 0%, 4%  { opacity: 0; transform: translateY(8px); } 7%, 34%  { opacity: 1; transform: none; } 36%, 100% { opacity: 0; transform: none; } }
@keyframes sc-m2 { 0%, 10% { opacity: 0; transform: translateY(8px); } 13%, 34% { opacity: 1; transform: none; } 36%, 100% { opacity: 0; transform: none; } }
@keyframes sc-m3 { 0%, 17% { opacity: 0; transform: translateY(8px); } 20%, 34% { opacity: 1; transform: none; } 36%, 100% { opacity: 0; transform: none; } }
@keyframes sc-m4 { 0%, 23% { opacity: 0; transform: translateY(8px); } 26%, 34% { opacity: 1; transform: none; } 36%, 100% { opacity: 0; transform: none; } }
/* Arrives, then goes — replaced by the message it was promising. */
@keyframes sc-type   { 0%, 28% { opacity: 0; transform: translateY(6px); } 30%, 33% { opacity: 1; transform: none; } 35%, 100% { opacity: 0; transform: none; } }
@keyframes sc-unread { 0%, 25% { opacity: 0; transform: translateY(6px); } 28%, 34% { opacity: 1; transform: none; } 36%, 100% { opacity: 0; transform: none; } }
@keyframes sc-dot    { 0%, 100% { opacity: 0.35; transform: translateY(0); } 50% { opacity: 1; transform: translateY(-3px); } }
/* Sent, then read. Back to sent at the end so the next pass starts honest. */
@keyframes sc-read   { 0%, 16% { color: var(--sc-tick-sent); } 19%, 34% { color: var(--sc-tick-read); } 36%, 100% { color: var(--sc-tick-sent); } }

/* Nobody is watching a background tab. Paused rather than stopped, so coming
   back to it resumes mid-conversation instead of snapping to the start. */
.auth-scene.scene-paused *, .auth-scene.scene-paused { animation-play-state: paused; }

/* Which step is on screen, said in colour. A cool wash for the two-factor and
   reset steps — the same panel, acknowledging that the page changed under it.
   Phase 3 puts the words in; this is the seam they hang on. */
.auth-scene::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(90% 70% at 68% 32%, rgba(12, 122, 190, 0.42) 0%, rgba(12, 122, 190, 0) 62%);
  opacity: 0; transition: opacity 0.6s ease;
}

/* A slow light behind everything, so the panel is never a flat rectangle —
   which matters most on a phone, where the band is all that is left of it.
   inset: -25% keeps the blob's own edges off screen at every point of the
   drift; alternate means it eases back rather than jumping home. */
.auth-scene::before {
  content: ''; position: absolute; inset: -25%; pointer-events: none;
  background: radial-gradient(closest-side, rgba(93, 231, 173, 0.28) 0%, rgba(93, 231, 173, 0) 100%);
  animation: scene-drift 26s ease-in-out infinite alternate;
}
@keyframes scene-drift {
  from { transform: translate3d(-8%, -6%, 0) scale(1); }
  to   { transform: translate3d(10%, 8%, 0) scale(1.18); }
}
/* BOTH PSEUDO-ELEMENTS BELONG BEHIND THE CONTENT. They are positioned and the
   stage and caption are not, so without this they paint on top — and the tint
   above is opaque enough that the two-factor step would have washed blue over
   its own headline. */
.scene-stage, .scene-caption { position: relative; z-index: 1; }
.auth-scene[data-scene="twofa"]::after,
.auth-scene[data-scene="forgot"]::after { opacity: 1; }

/* ---- which scene, and what it says --------------------------------------
 *
 * data-for on a block is the list of steps it belongs to; data-scene on the
 * panel is the step showing. [data-for~="…"] matches a word inside that list,
 * so the conversation can serve two steps without being written twice.
 *
 * THE :not([data-scene]) LINE IS THE ONE THAT MATTERS. It is what the panel
 * falls back to before the scene script runs — and if that script never runs at
 * all, which is the state a blocked or broken file leaves the page in. Without
 * it the left half of a sign-in page would be an empty column. */
.scene-stage > [data-for], .scene-caption > [data-for] { display: none; }
.auth-scene:not([data-scene])    [data-for~="login"],
.auth-scene[data-scene="login"]  [data-for~="login"],
.auth-scene[data-scene="signup"] [data-for~="signup"],
.auth-scene[data-scene="twofa"]  [data-for~="twofa"],
.auth-scene[data-scene="forgot"] [data-for~="forgot"] {
  display: flex;
  /* display cannot be transitioned, so the arriving block plays its own way in.
     Reads as a crossfade without the absolute positioning one would need. */
  animation: sc-enter 0.45s ease both;
}
@keyframes sc-enter { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

.scene-caption { width: min(360px, 100%); color: #fff; }
.scene-caption > div { flex-direction: column; gap: 8px; }
.scene-caption h2 { margin: 0; font-size: 22px; font-weight: 800; line-height: 1.25; letter-spacing: -0.01em; }
.scene-caption p { margin: 0; font-size: 13.5px; line-height: 1.5; color: rgba(255, 255, 255, 0.72); }
.scene-points { margin: 6px 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 7px; }
.scene-points li { position: relative; padding-inline-start: 20px; font-size: 13px; color: rgba(255, 255, 255, 0.80); }
.scene-points li::before {
  content: ''; position: absolute; inset-inline-start: 0; top: 6px;
  width: 7px; height: 7px; border-radius: 50%; background: #5de7ad;
}

/* ---- the two-factor stage: six cells, then the lock ---------------------- */
/* Light, like the window on the other two steps and like the product. */
.scene-code, .scene-mail {
  flex-direction: column; align-items: center; justify-content: center; gap: 22px;
  padding: 34px 28px; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.16), 0 12px 28px rgba(0, 0, 0, 0.22), 0 40px 80px rgba(0, 0, 0, 0.30);
}
.sk-lock { width: 40px; height: 40px; }
.sk-cells { display: flex; gap: 8px; }
.sk-cells span {
  width: 32px; height: 42px; border-radius: 8px; border: 1px solid;
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; font-weight: 700; color: var(--ink);
}
/* Delays are right here rather than in the percentages, unlike the conversation
   loop: six identical cells filling one after another IS a ripple, and the
   slight overlap at the loop boundary is what typing a code looks like. */
.sk-cells span { animation: sk-fill 9s ease-in-out infinite; }
.sk-cells span:nth-child(2) { animation-delay: 0.22s; }
.sk-cells span:nth-child(3) { animation-delay: 0.44s; }
.sk-cells span:nth-child(4) { animation-delay: 0.66s; }
.sk-cells span:nth-child(5) { animation-delay: 0.88s; }
.sk-cells span:nth-child(6) { animation-delay: 1.10s; }
.sk-lock { animation: sk-lock 9s ease-in-out infinite; }
/* The digit arrives with the cell that holds it — an empty box that lights up is
   a placeholder, a box with a number in it is somebody typing a code. */
@keyframes sk-fill {
  0%, 8%    { background: var(--ground); border-color: var(--line); color: transparent; }
  14%, 84%  { background: var(--accent-soft); border-color: var(--accent); color: var(--ink); }
  90%, 100% { background: var(--ground); border-color: var(--line); color: transparent; }
}
/* Closes once the last cell is in — about 26% of the way through. */
@keyframes sk-lock {
  0%, 30%   { color: #c3d0cb; }
  38%, 86%  { color: var(--accent); }
  92%, 100% { color: #c3d0cb; }
}

/* ---- the reset stage: a code leaving in an envelope ---------------------- */
.sm-envelope { width: 60px; height: 45px; color: var(--ink-soft); animation: sm-send 7s ease-in-out infinite; }
.sm-code {
  display: flex; gap: 6px; padding: 10px 14px; border-radius: 10px;
  background: var(--ground); border: 1px solid var(--line);
  animation: sm-lift 7s ease-in-out infinite backwards;
}
.sm-code i {
  font-style: normal; font-size: 15px; font-weight: 700; letter-spacing: 0.04em; color: var(--ink);
  animation: sm-digit 7s ease-in-out infinite backwards;
}
.sm-code i:nth-child(2) { animation-delay: 0.08s; }
.sm-code i:nth-child(3) { animation-delay: 0.16s; }
.sm-code i:nth-child(4) { animation-delay: 0.24s; }
.sm-code i:nth-child(5) { animation-delay: 0.32s; }
.sm-code i:nth-child(6) { animation-delay: 0.40s; }
@keyframes sm-send  { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
@keyframes sm-lift  { 0%, 10% { opacity: 0; transform: translateY(16px) scale(0.94); } 22%, 74% { opacity: 1; transform: none; } 86%, 100% { opacity: 0; transform: translateY(-10px); } }
@keyframes sm-digit { 0%, 26% { opacity: 0; } 34%, 74% { opacity: 1; } 86%, 100% { opacity: 0; } }

/* The composition is the base state, so switching every animation off lands on
   the finished conversation rather than on an empty card. The two stages added
   with it have no base fill, so they are given their settled one here. */
@media (prefers-reduced-motion: reduce) {
  .sv, .sw-rb1, .sw-rb2, .sw-rb3, .sw-msg, .sw-unread, .sc-ticks, .sc-typing span,
  .sk-cells span, .sk-lock, .sm-envelope, .sm-code, .sm-code i { animation: none; }
  /* The tour stops on the inbox. With every view animation off all three would
     stack on top of each other at full opacity, so the two behind are hidden
     outright and the rail is lit for the one that is left. */
  .sv-mail, .sv-tickets { display: none; }
  .sw-rb1 { background: var(--accent-soft); }
  /* Matched at the same weight as the rule that sets the entry animation
     (0,3,0) and placed after it. A lighter selector here would simply lose, and
     the one animation somebody asked not to see would be the one still playing. */
  .auth-scene[data-scene] [data-for],
  .auth-scene:not([data-scene]) [data-for] { animation: none; }
  .sc-typing { display: none; }   /* a typing indicator that never resolves is a lie */
  .auth-scene::before { animation: none; }
  .sc-ticks { color: var(--sc-tick-read); }
  .sk-cells span { background: var(--accent-soft); border-color: var(--accent); }
  .sk-lock { color: var(--accent); }
  .auth-scene::after { transition: none; }
}

/* ---- the phone ----------------------------------------------------------
 *
 * One column, and the scene becomes a short band above the card rather than
 * disappearing: the headline is the part worth carrying over, and the
 * conversation choreography needs vertical room this viewport does not have.
 *
 * clamp(64px, 16dvh, 116px) — dvh does the hard work here. The login step runs
 * about 459px against roughly 579px of visible viewport on an iPhone SE, so
 * there are ~120px to spend; signup runs about 611px and was already scrolling
 * before any of this. When the keyboard opens, dvh shrinks and the band shrinks
 * with it, which is the case that matters on the two-factor step.
 *
 * NO env(safe-area-inset-*): this page's viewport is not viewport-fit=cover, so
 * those all resolve to 0 and the browser letterboxes the notch itself. */
@media (max-width: 860px) {
  .auth-split { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 1fr; }
  .auth-scene {
    min-height: clamp(64px, 16dvh, 116px);
    justify-content: center; gap: 0; padding: 14px 20px;
  }
  .scene-stage { display: none; }
  .scene-caption { width: 100%; text-align: center; }
  .scene-caption h2 { font-size: 17px; }
  .scene-caption p, .scene-points { display: none; }

  /* THE SCROLLER MOVES TO THE PAGE. On desktop .auth-wrap owns the scroll; here
     it would claim a full viewport of its own BELOW the band, pushing the card
     down by the band's height and putting the submit button under the fold.
     Handing the scroll to the document also lets the browser collapse its URL
     bar and scroll a focused field into view the way it does everywhere else —
     which is the behaviour the two-factor step depends on. */
  .auth-wrap { min-height: 0; max-height: none; overflow-y: visible; }
}

/* No vertical room at all: a phone on its side, or one with the keyboard up.
   The band is the first thing to go — it is decoration, and the code field and
   the button under it are not.

   Width AND height, not height alone. A laptop window dragged short is still
   1440px wide with room for both columns; on height alone it would have thrown
   away the left half of the page for no reason. */
@media (max-width: 860px) and (max-height: 620px) {
  .auth-scene { display: none; }
}

/* dvh, not vh: on iOS/Android the URL bar is inside 100vh, so a plain 100vh puts
   the bottom of a tall card behind it — and this page now carries the two-factor
   step, where the button below the code field is the one thing that must be
   reachable. The vh line stays first as the fallback for browsers without dvh.
   overflow-y:auto because .auth-wrap is the scroller here (body is overflow:hidden). */
.auth-wrap {
  min-height: 100vh; min-height: 100dvh; max-height: 100dvh; overflow-y: auto;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; padding: 24px;
  /* The page's gradient, moved off body and onto this half. On body it ran
     corner to corner, so once the scene covered the top-left the only part left
     showing was the flat end of it. Each half owns its own ground now. */
  background: linear-gradient(160deg, var(--accent-soft) 0%, var(--ground) 55%);
}
/* max-width: 100%, not 94vw. 94vw is measured against the VIEWPORT while this
   card sits inside .auth-wrap's 24px padding, so on a 360px phone it asked for
   338px inside a 312px box and hung 26px over the edge — and .auth-wrap sets
   overflow-y, which makes overflow-x compute to auto rather than visible, so
   the whole sign-in page scrolled sideways. Percent is measured against the
   containing block, which is the box it actually has to fit in. */
.auth-card {
  width: 400px; max-width: 100%; background: var(--panel);
  border: 1px solid var(--line); border-radius: 16px; padding: 28px 28px 24px;
  /* Two layers and no hue. The single green-tinted shadow read as a glow when
     the card had a mint page behind it; against the scene it just looked like
     the card was leaking. Close contact shadow plus a wide soft one is what
     gives it a height instead of a colour. */
  box-shadow: 0 1px 2px rgba(16, 40, 32, 0.05), 0 18px 44px rgba(16, 40, 32, 0.10);
}
/* PHONE PADDING, AND IT HAS TO SIT *AFTER* THE TWO RULES IT OVERRIDES.
 *
 * This block used to live above .auth-wrap and .auth-card. A media query adds
 * no specificity, so the later base rules won and none of this applied — the
 * card kept its 28px desktop padding inside .auth-wrap's 24px on every phone,
 * which is 104px of chrome on a 320px screen and left a 216px column for the
 * fields. The "edge to edge" the comment below describes had never happened.
 *
 * Edge to edge. At 360px the card was leaving an 11px sliver of background down
 * each side, which reads as a mistake rather than as a card. */
@media (max-width: 480px) {
  .auth-wrap { padding: 14px; }
  .auth-card { border-radius: 12px; padding: 20px 18px 18px; }
}

/* THE TAB LABELS, ON THE NARROWEST PHONES.
 *
 * "Create workspace" is two words and 13px, and the tab is half of a 256px
 * column — so at 320px it wrapped onto two lines and made the pill 90px tall,
 * which is taller than the two form fields under it. German ("Arbeitsbereich
 * erstellen") and Spanish are longer still, so this is not an English-only
 * problem.
 *
 * nowrap AND a smaller type size, because nowrap alone would simply overflow
 * the pill instead of wrapping inside it. The 44px minimum below still applies,
 * so the tap target does not shrink with the text. */
@media (max-width: 380px) {
  .auth-tab { font-size: 11.5px; padding: 8px 6px; white-space: nowrap; }
}

.auth-brand { font-weight: 800; font-size: 21px; letter-spacing: -0.01em; margin-bottom: 20px; }
.auth-tabs { display: flex; gap: 6px; background: var(--ground); border-radius: 999px; padding: 4px; margin-bottom: 18px; }
.auth-tab {
  flex: 1; border: none; background: none; border-radius: 999px;
  padding: 8px 10px; font-weight: 700; font-size: 13px; color: var(--ink-soft);
  cursor: pointer; transition: background 0.15s ease, color 0.15s ease;
}
.auth-tab.active { background: var(--accent); color: #fff; }
.auth-form label { font-size: 12.5px; }
.auth-form input {
  width: 100%; box-sizing: border-box; padding: 11px 12px;
  border: 1px solid var(--line); border-radius: 9px; background: var(--ground);
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.auth-form input::placeholder { color: var(--ink-soft); opacity: 0.6; }
.auth-form input:hover { border-color: #cfdad5; }
/* A ring rather than an outline, so the focused field reads as one shape. The
   border carries the colour and the shadow carries the halo — both visible, so
   this is not the outline:none that leaves keyboard users guessing. */
.auth-form input:focus {
  outline: none; background: var(--panel);
  border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
}
/* box-shadow is how the ring is drawn, and box-shadow is also the only way to
   paint over Chrome's autofill yellow — so a focused autofilled field has to
   ask for both, in that order, or one of the two silently wins. */
.auth-form input:-webkit-autofill {
  -webkit-text-fill-color: var(--ink);
  box-shadow: 0 0 0 1000px var(--ground) inset;
  caret-color: var(--ink);
}
.auth-form input:-webkit-autofill:focus {
  box-shadow: 0 0 0 1000px var(--panel) inset, 0 0 0 3px var(--accent-soft);
}
/* Windows high-contrast throws every author colour away, including the ring
   above. Handing the outline back is the only thing that still marks focus. */
@media (forced-colors: active) {
  .auth-form input:focus { outline: 2px solid; outline-offset: 1px; }
}
.auth-submit {
  width: 100%; padding: 12px; margin-top: 8px; border-radius: 9px;
  font-size: 14px; font-weight: 700;
  box-shadow: 0 6px 16px rgba(15, 122, 84, 0.24);
  transition: box-shadow 0.15s ease, opacity 0.15s ease;
}
/* The sign-in script disables this for the length of every request and swaps the
   label to "Please wait…". Until now the button looked exactly the same while
   that was true, so the only clue a login was in flight was the word. */
.auth-submit[disabled] { opacity: 0.6; cursor: default; box-shadow: none; filter: none; }
/* The two-factor step lives on this page. Below 16px iOS Safari zooms the page
   when the field takes focus, which on a one-shot code with a 30-second life is
   the difference between typing it and chasing the viewport. 44px is the tap
   target Apple and Android both ask for. */
/* KEYED ON THE POINTER AS WELL AS THE WIDTH, which is the rest of this file's
   convention (six other blocks do it) and was the one thing this page did not.
   A touch tablet at 800px is wider than 640 and has exactly the same fingers:
   it was getting the 9px desktop fields, and under 16px that is the iOS zoom
   this comment warns about, on the code field it warns about it for.
   Kept as a union rather than a swap so no narrow desktop window loses a rule
   it has today. */
@media (pointer: coarse), (max-width: 640px) {
  .auth-form input { font-size: 16px; min-height: 44px; }
  .auth-submit { min-height: 44px; }
  .auth-hint a { display: inline-block; padding: 6px 0; }
  /* Log in / Create workspace: the two most-tapped controls on the page, and
     the only ones this block never covered. At 7px of padding on 13px text they
     stood about 31px tall — under the target every other control here meets. */
  .auth-tab { min-height: 44px; }
}
.auth-hint { font-size: 12px; color: var(--ink-soft); text-align: center; margin-top: 10px; }
/* There is no global rule for <a> in this stylesheet, so every link on this card
   — "Forgot password?", "Lost your phone?", "Back to log in" — was rendering in
   the browser's default blue, the one colour on the page that belongs to nobody. */
.auth-hint a { color: var(--accent); font-weight: 600; text-decoration: none; }
.auth-hint a:hover { text-decoration: underline; }
/* Sign in with Google — the divider and the slot Google renders its button into.
   The button itself is an iframe from accounts.google.com: its size, font and
   wording are Google's (their brand guidelines require it), so the only job here
   is to centre it and to own the "or" underneath. */
.auth-google { margin-bottom: 16px; }
#google-btn { display: flex; justify-content: center; min-height: 40px; }
/* A rule on both sides of the word, drawn with the row itself rather than with a
   border under a pseudo-element, so it stays centred in Arabic with no RTL rule. */
.auth-or {
  display: flex; align-items: center; gap: 10px;
  margin-top: 16px; font-size: 12px; color: var(--ink-soft);
}
.auth-or::before, .auth-or::after {
  content: ''; flex: 1; height: 1px; background: var(--line);
}
.auth-error {
  margin-top: 14px; background: #fdeaea; color: var(--danger); border: 1px solid #f3c8c8;
  border-radius: 9px; padding: 10px 12px; font-size: 13px; line-height: 1.45;
}
/* Neutral info note (e.g. "if an account exists, a code is on its way"). */
.auth-note {
  margin-top: 14px; background: var(--accent-soft); color: var(--accent); border: 1px solid #c4e6d6;
  border-radius: 9px; padding: 10px 12px; font-size: 13px; line-height: 1.45;
}
.auth-foot { font-size: 12.5px; color: var(--ink-soft); }

/* Mobile-only conversation controls — hidden on desktop, revealed in the
   phone media query below (WhatsApp-style back arrow + details toggle). */
.conv-back, .conv-info {
  display: none; border: none; background: none; cursor: pointer;
  font-size: 20px; line-height: 1; padding: 4px 8px; border-radius: 8px; color: var(--ink);
}
.conv-back:hover, .conv-info:hover { background: var(--ground); }
.conv-head-title { min-width: 0; }
.details-mhead { display: none; }   /* mobile-only header for the details sheet */

/* responsive: keep the conversation usable on narrow viewports / high zoom */
#conv { min-width: 340px; }
/* Tablet / small desktop: no room for a permanent 3rd pane — the details panel
   becomes an on-demand right slide-over, toggled by the ℹ️ in the conv header. */
@media (max-width: 1220px) {
  #details { display: none; }
  /* release the base 320px hard-lock so the flex-basis below actually applies */
  #chatlist { flex: 0 0 300px; width: auto; min-width: 0; max-width: none; }
  .plan-grid { grid-template-columns: 1fr; }
  .conv-info { display: inline-flex; align-items: center; }
  body.m-details #details {
    display: flex; flex-direction: column; position: fixed;
    top: 50px; inset-inline-end: 0; bottom: 0; width: min(380px, 100vw); z-index: 60;
    background: var(--panel); overflow-y: auto;
    border-inline-start: 1px solid var(--line); box-shadow: -14px 0 34px rgba(20, 28, 25, 0.16);
    /* The rail sits at the bottom of the phone layout and this sheet is fixed to
       the viewport, so without this the last 58px of the panel lives behind it —
       and whatever is last (a follow-up line, the AI summary) can never be read
       or tapped. Pad rather than shorten, so the sheet still fills the screen. */
    padding-block-end: 58px;
    scroll-padding-block-end: 58px;
  }
  .details-mhead {
    display: flex; align-items: center; gap: 8px; position: sticky; top: 0; z-index: 1;
    background: var(--panel); border-bottom: 1px solid var(--line); padding: 10px 12px; font-weight: 700;
  }
  /* The mobile sheet has its own sticky header above the tabs. */
  .det-tabs { padding-block-start: 8px; }
  .details-close { border: none; background: none; font-size: 20px; cursor: pointer; padding: 2px 6px; color: var(--ink); }
}
@media (max-width: 860px) {
  #chatlist { flex: 0 0 260px; }
  .msg { max-width: 85%; }
  .dash-grid { grid-template-columns: 1fr; }
  .settings-nav { flex: 0 0 150px; }
}
/* Tablet portrait (≤768): a touch more breathing room in the two panes. */
@media (max-width: 768px) and (min-width: 641px) {
  #chatlist { flex: 0 0 250px; }
  .msgs { padding: 16px 18px; }
}

/* ---------- mobile: WhatsApp-style single pane + bottom nav (≤640px) ---------- */
@media (max-width: 640px) {
  /* dvh (dynamic viewport) keeps the bottom nav above the mobile browser bar; a
     plain 100vh leaves it hidden behind the URL/toolbar on iOS/Android. */
  #app { height: 100dvh; }
  #conv { min-width: 0; }               /* the 340px floor breaks a phone layout */

  /* compact top bar */
  #topbar { height: 50px; flex-basis: 50px; padding: 0 12px; }
  /* Scoped to the top bar. Unscoped it also caught .auth-brand .brand-sub on the
     sign-in card, which left the brand line reading "WAScope ·" — tagline gone,
     separator dot still there — on every phone. */
  #topbar .brand-sub, .me-name { display: none; }
  .topbar-right { gap: 6px; }

  /* left icon rail → bottom navigation bar */
  #main { flex-direction: column; }
  #stage { order: 1; flex: 1 1 auto; min-height: 0; }
  #view { flex: 1 1 auto; min-height: 0; }
  /* FIVE LABELLED ITEMS, not nine bare icons: four pages (syncPhoneBar marks
     them .on-phone) and More, which lists the rest with a line each. */
  #rail {
    order: 2; flex: 0 0 60px; height: 60px; width: 100%; overflow: visible;
    flex-direction: row; justify-content: space-around; align-items: stretch;
    gap: 0; padding: 0 4px; border-inline-end: none; border-top: 1px solid var(--line);
  }
  #rail .rail-btn:not(.on-phone), #rail .rail-sep, #rail .rail-setup { display: none; }
  #rail .rail-more { display: flex; }
  #rail .rail-btn.active { background: none; }
  #rail .rail-more.active { color: var(--accent); }
  .rail-hover { display: none; }
  /* flex the buttons so more items (Inbox/Briefing/Mail/Contacts/Tickets/Rules/
     Dashboard/Settings) share the width evenly and never overflow a narrow phone.
     Eight is the most that can ever be visible — Briefing hides without the 'ai'
     feature and Contacts hides without chat.read — and at 320px eight buttons on
     a 56px cap would want 448px. flex-basis:0 with min-width:0 lets them divide
     whatever there is instead, so the bar shrinks rather than scrolls: a nav item
     pushed off the edge of a bottom bar is a nav item that does not exist. */
  .rail-btn, .rail-more { flex: 1 1 0; width: auto; min-width: 0; padding: 8px 0 6px; border-radius: 0; justify-content: center; }
  .rail-gear { margin: 0; }

  /* inbox: one pane at a time — list, or (when a chat is open) the conversation */
  /* Unlock the base 320px hard-lock so the list fills the phone width (was
     capped at 320px, leaving a gap at 375/425 while 320 happened to fit). */
  #chatlist { flex: 1 1 auto; width: auto; min-width: 0; max-width: none; border-inline-end: none; }
  #conv { display: none; }
  #details { display: none; }
  body.has-active-chat #chatlist { display: none; }
  body.has-active-chat #conv { display: flex; }
  /* Opening a chat hands the whole screen to the conversation — hide the app top
     bar (WAScope brand etc.) so the chat's own header sits at the very top, like
     WhatsApp. Covers the details sheet too (m-details only exists with a chat). */
  body.has-active-chat #topbar { display: none; }

  /* conversation header controls */
  .conv-back { display: inline-flex; align-items: center; }
  /* Drop the ℹ️ button on phones: the search icon takes its slot, and tapping the
     chat title already opens the details sheet. */
  .conv-info { display: none; }
  .conv-head { padding: 0 6px; gap: 8px; }
  .conv-head-title { flex: 1; cursor: pointer; }
  .conv-title, .conv-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .conv-head-right { gap: 4px; }
  .conv-head-right .ai-btn { font-size: 11px; padding: 5px 8px; }
  .conv-head-right .icon-btn { font-size: 17px; padding: 5px; }   /* the 🔍 search icon */

  /* on a phone the details sheet fills the screen (override the tablet
     slide-over) — full width, from the very top (the app bar is hidden while a
     chat is open) down to the bottom nav */
  body.m-details #details {
    position: fixed; z-index: 60;
    inset-inline-start: 0; inset-inline-end: 0; top: 0; bottom: 58px; width: auto;
    border-inline-start: none; box-shadow: none;
    padding: 0;                 /* full-bleed so the header is flush at the top */
  }
  /* the base 16px padding moves onto the sections, so the sticky "Chat details"
     header sits at the very top — exactly like the chat header does. */
  body.m-details .details-section { padding-inline-start: 16px; padding-inline-end: 16px; }
  body.m-details .details-section:first-of-type { padding-top: 14px; }
  /* Properties tab isn't made of .details-sections, so pad the tab bar and the
     values panel to match on the full-bleed mobile sheet. Keep the end padding
     tight so the ⋯ button lands under the thumb, not inset from the edge. */
  body.m-details .det-tabs { padding-inline: 16px 10px; }
  /* Full-width tap targets on a phone: a card header is the whole row. */
  body.m-details .det-cardhead { padding-block: 14px; }
  body.m-details .det-more { padding: 8px 10px; font-size: 18px; }
  body.m-details .det-menu button { padding: 11px 6px; font-size: 14px; }
  /* 16px, so iOS does not zoom the sheet when a control takes focus. */
  body.m-details .det-strip .assignee-select,
  body.m-details .crm-input { font-size: 16px; }
  body.m-details .det-addlabel, body.m-details .crm-addfu { min-height: 32px; }
  body.m-details .cp-panel,
  body.m-details .det-empty-props { padding: 14px 16px 16px; }

  /* roomier thread */
  .msg { max-width: 88%; }
  .msgs { padding: 12px; }

  /* Drop the "Chats" heading (the search bar reads as the header), and float the
     new-chat button as a WhatsApp-style FAB above the bottom nav. It lives inside
     #chatlist, so it auto-hides on the conversation pane. */
  .chatlist-title { display: none; }
  .chatlist-top { display: contents; }
  .new-chat-btn {
    position: fixed; inset-inline-end: 16px; bottom: 74px; z-index: 55;
    width: 54px; height: 54px; border-radius: 50%; border: none;
    background: var(--accent); color: #fff; font-size: 22px;
    box-shadow: 0 6px 18px rgba(15, 122, 84, 0.42);
  }
  .new-chat-btn:hover { background: var(--accent); border: none; filter: brightness(1.06); }

  /* chat list: comfortable rows; filter chips scroll sideways, never wrap */
  .chatlist-head { padding: 10px 12px 8px; }
  .filter-row { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding-bottom: 2px; }
  .filter-row::-webkit-scrollbar { display: none; }
  .chip, .phone-filter { flex: 0 0 auto; }
  .chat-item { padding: 11px 12px; }
  .composer { padding: 8px 10px 10px; }

  /* modals: nearly full-width so forms are usable */
  .modal { width: 94vw; max-width: 94vw; }

  /* non-inbox views scroll within the view area; stack the ticket board */
  body[data-view="inbox"] #view { overflow: hidden; }
  body:not([data-view="inbox"]) #view { overflow-y: auto; display: block; }
  .kanban { grid-template-columns: 1fr; }
  .ticket-panel { width: 100vw; }
  .page { padding: 14px; overflow-x: hidden; }   /* no sideways scroll on phones */
  body { overflow-x: hidden; }                    /* hard backstop: nothing scrolls the page sideways */

  /* Workspace dashboard: stat cards go 2-up and compact, the panels stack (via
     the 860px rule), and the wide 6-column team table scrolls sideways inside
     its card instead of stretching the page. */
  .page h2 { font-size: 18px; }
  .stat-row { grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 16px; }
  .stat { padding: 11px 12px; }
  .stat .s-val { font-size: 21px; overflow-wrap: anywhere; }   /* big counts never overflow */
  .stat .s-label { font-size: 11px; line-height: 1.3; }
  /* Stack the whole dashboard in one real column. A CSS grid track keeps
     min-width:auto, so any wide child (a table label, a device stat) can push
     the single column past the phone width and get clipped by overflow-x:hidden
     — the "right half hidden" bug. Plain block stacking can't overflow, and the
     clamps below stop any inner content from forcing width. */
  .dash-grid { display: block; }
  .dash-grid > * { min-width: 0; max-width: 100%; margin-bottom: 12px; }
  .dash-grid > *:last-child { margin-bottom: 0; }
  .card, .stat-row, .dev-cards, .bars, table.agents { min-width: 0; max-width: 100%; }
  .tbl-scroll { min-width: 0; max-width: 100%; overflow-x: hidden; }
  .card { padding: 14px; }                 /* dashboard/settings cards, tighter on phones */
  .card h3 { font-size: 14px; }
  .sect-h { margin: 18px 0 10px; font-size: 14px; }
  .bars { height: 100px; }

  /* Team: turn the 6-column table into a stack of agent cards — each row a card,
     each cell a "Label ....... value" line — the proper phone pattern (no
     sideways scroll). The <th> header row is hidden; td::before carries labels. */
  /* The <table> and the browser-injected <tbody> must both go block, or the
     row-group keeps the block <tr>s shrink-to-fit (each card only ~135px wide,
     leaving empty space on the right). Rows then stretch to the full card. */
  table.agents, table.agents tbody { display: block; width: 100%; }
  table.agents tr:first-child { display: none; }            /* the header row */
  table.agents tr {
    display: block; width: 100%; box-sizing: border-box;
    background: var(--ground); border: 1px solid var(--line);
    border-radius: 10px; padding: 10px 12px; margin-bottom: 10px;
  }
  table.agents td { box-sizing: border-box; }
  table.agents td {
    display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
    padding: 6px 0; border: none; font-size: 13px; font-weight: 700; color: var(--ink);
    overflow-wrap: anywhere;             /* long values wrap instead of scrolling the page */
  }
  /* label sits fixed on the left, the value (bold) on the right */
  table.agents td::before {
    content: attr(data-label); flex: none; white-space: nowrap;
    color: var(--ink-soft); font-weight: 500; font-size: 12.5px;
  }
  table.agents td.ag-name {
    font-size: 15px; font-weight: 800; padding: 0 0 8px; margin-bottom: 6px;
    border-bottom: 1px solid var(--line); justify-content: flex-start; color: var(--ink);
  }
  table.agents td.ag-name::before { display: none; }        /* name is the card title */
  table.agents .unread-dot { margin-inline-start: 6px; }

  /* Onboarding health — by device: one device card per row (the base 320px min
     overflows a phone), and 2-up stats inside each card so labels don't wrap.
     NOTE: the base .dev-* rules are defined LATER in the file than this media
     query, so a bare .dev-cards here loses on source order. Scoping under
     .dash-grid raises specificity (0,2,0 > 0,1,0) so these actually win. */
  .dash-grid .dev-cards { grid-template-columns: 1fr; }
  /* The card is a grid item (min-width:auto) — a long number or an unbreakable
     word (the phone number, "conversations") makes it wider than the phone and
     it spills off the right. Clamp the card and everything inside it. */
  .dash-grid .dev-card { padding: 12px 14px; min-width: 0; max-width: 100%; overflow: hidden; }
  .dash-grid .dev-head { flex-wrap: wrap; min-width: 0; } /* long name never crushes the status */
  .dash-grid .dev-name { overflow-wrap: anywhere; min-width: 0; }
  .dash-grid .dev-num { overflow-wrap: anywhere; }        /* the phone number wraps, never overflows */
  /* auto-fit adapts to phone size on its own: 2-up on s/m (320/375), 3-up on l
     (425+) — the min tracks the widest label ("in blob storage"). */
  .dash-grid .dev-grid { grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 10px 8px; min-width: 0; }
  .dash-grid .dev-grid > div { min-width: 0; }            /* nested grid items shrink too */
  .dash-grid .dev-grid b { font-size: 16px; overflow-wrap: anywhere; }
  .dash-grid .dev-grid span { overflow-wrap: anywhere; }
  .dash-grid .dev-media { min-width: 0; max-width: 100%; }

  /* Groups controls: search takes the full width, the selects/buttons flow below. */
  .grp-controls { gap: 6px; }
  .grp-search { flex: 1 1 100%; }
  .grp-controls select { flex: 1 1 auto; min-width: 0; }
  .grp-pager { justify-content: center; }

  /* Messages-by-hour: 24 slim bars, so shrink the gap so nothing gets clipped. */
  .bars { gap: 2px; }

  /* Settings: the side-nav becomes a horizontal scrolling pill-tab bar, with the
     body full-width below (mobile settings pattern). */
  .settings { flex-direction: column; }
  .settings-nav {
    flex: 0 0 auto; width: auto; flex-direction: row; align-items: center; gap: 6px;
    padding: 8px 12px; overflow-x: auto; scrollbar-width: none;
    border-inline-end: none; border-bottom: 1px solid var(--line);
  }
  .settings-nav::-webkit-scrollbar { display: none; }
  .settings-nav h3 { display: none; }
  .settings-tab {
    flex: 0 0 auto; white-space: nowrap; padding: 7px 14px;
    border-radius: 999px; background: var(--ground);
  }
  .settings-tab.active { background: var(--accent); color: #fff; }
  .settings-plan {
    margin: 0; border-top: none; padding: 0 2px; flex: 0 0 auto;
    flex-direction: row; align-items: center; gap: 8px;
  }
  .settings-plan-name { white-space: nowrap; font-size: 12px; }
  .settings-plan .btn { white-space: nowrap; }
  .settings-body { padding: 16px 14px; }
  .settings-head { flex-wrap: wrap; }
  .settings-head h2 { font-size: 18px; }

  /* Phones: name + status on line 1, sync badge and action buttons each on
     their own full-width row below — a clean stacked card. */
  .phone-list { max-width: none; }
  .phone-row { flex-wrap: wrap; align-items: center; row-gap: 8px; padding: 12px; }
  .phone-info { flex: 1 1 auto; min-width: 0; }
  .phone-name, .phone-num { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .pstatus { flex: 0 0 auto; margin-inline-start: auto; }
  .phone-row .sync-badge { flex: 1 1 100%; }
  .phone-actions { flex: 1 1 100%; }
  .phone-actions .btn { flex: 1 1 auto; }

  /* AI connectors.
     The address is one long unbreakable string, so on a phone it wraps and the
     copy button takes its own full-width row — a 40px-wide "Copy" beside a
     scrolling code box is a button nobody hits. */
  .mcp-url-card, .mcp-guides, .mcp-guide, .mcp-facts { max-width: none; }
  .mcp-url { flex-wrap: wrap; }
  .mcp-url code { flex: 1 1 100%; white-space: normal; word-break: break-all; }
  .mcp-url .btn { flex: 1 1 100%; }
  /* One guide at a time. auto-fit would still fit two 280px columns at 620px,
     and two columns of numbered steps on a phone is neither of them read. */
  .mcp-guides { grid-template-columns: 1fr; }
  /* Connection rows stack like the phone list above: name and badge first, the
     sub-line under it, Revoke on its own full-width row. These carry more than
     the extension rows do — a badge and a three-part sub-line — so left as a
     flex row they squeeze the name to nothing. */
  /* Webhooks. Same treatment as the connection rows below: everything that is a
     row on a desktop becomes a stack, because a card with five buttons and a
     URL in it has nowhere to go at 375px. */
  .wh-card, .wh-form, .wh-scope, .wh-hs { max-width: 100%; }
  .wh-top { flex-direction: column; align-items: stretch; gap: 8px; }
  .wh-acts { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
  .wh-acts .btn { width: 100%; }
  .wh-log-ev { min-width: 0; flex: 1 1 100%; }
  .wh-log-row .btn { flex: 1 1 100%; }
  .wh-f-acts .btn { flex: 1 1 100%; }
  .wh-hs-head { flex-wrap: wrap; }
  .wh-labels { gap: 10px; }
  #mcp-tokens .ext-row { flex-wrap: wrap; row-gap: 8px; }
  #mcp-tokens .ext-row-info { flex: 1 1 100%; min-width: 0; }
  #mcp-tokens .ext-row .btn { flex: 1 1 100%; }

  /* Labels, with their automation config, on a phone.
     The row is a colour swatch, a name, a switch and a Delete button — four
     things that cannot share one line at this width, so the name takes the
     first line and the controls the second. The config fields below are already
     stacked; they only need to stop being 720px wide. */
  .label-card { padding: 10px; }
  .label-card .label-row { flex-wrap: wrap; row-gap: 8px; }
  .label-card .label-name-input { flex: 1 1 auto; min-width: 0; }
  .label-card .lb-auto { flex: 1 1 100%; order: 3; }
  .label-card .label-row .btn { order: 2; }
  .lb-auto-cfg, .lb-rule, .lb-stats { max-width: none; }
  /* Six columns of counts will not fit a phone, so the table scrolls inside its
     own box rather than pushing the page sideways. */
  .lb-stats-table { display: block; overflow-x: auto; white-space: nowrap; }
  .lb-stats-head .btn { flex: 1 1 100%; }
  .lb-rule .btn { flex: 1 1 100%; }

  /* Suggested labels: one chip per row, so the buttons are reachable rather
     than squeezed off the end by a long label name. */
  .lsug { flex: 1 1 100%; }
  .lsug-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  .lsug-why { flex: 1 1 auto; max-width: none; }
  .lsug .btn.sm { flex: 0 0 auto; }

  /* Quick replies + labels: fields stack instead of shrinking to nothing */
  .inline-form, .qr-row, .label-row { flex-wrap: wrap; max-width: none; }
  .qr-list, .label-list, .inline-form { max-width: none; }
  .qr-cmd-input { width: auto; flex: 1 1 120px; }
  .qr-body-input { flex: 1 1 100%; }
  .inline-form > .btn, .qr-row > .btn { flex: 1 1 100%; }

  /* Team: the wide 7-column table reflows into one card per member */
  .tbl-scroll { overflow-x: visible; }
  .team-table { border: none; overflow: visible; background: none; }
  .team-table, .team-table tbody { display: block; }
  .team-table tr {
    display: block; background: var(--panel); border: 1px solid var(--line);
    border-radius: 10px; margin-bottom: 10px; padding: 8px 12px;
  }
  .team-table tr:first-child { display: none; }         /* the header row */
  .team-table td {
    display: flex; justify-content: space-between; align-items: center; gap: 14px;
    border: none; padding: 6px 0; text-align: end;
  }
  .team-table td::before { content: attr(data-label); font-weight: 600; color: var(--ink-soft); text-align: start; white-space: nowrap; }
  .team-table td[data-label="Name"] {
    justify-content: flex-start; text-align: start; font-weight: 700; font-size: 14px;
    border-bottom: 1px solid var(--line); margin-bottom: 4px; padding-bottom: 8px;
  }
  .team-table td[data-label="Name"]::before, .team-table td[data-label=""]::before { display: none; }
  .team-table td[data-label=""] { justify-content: flex-end; padding-top: 6px; }
  /* Two actions on a phone: full-width tap targets rather than a cramped pair. */
  .team-table td[data-label=""] .row-actions { width: 100%; }
  .team-table td[data-label=""] .row-actions .btn { flex: 1; min-height: 36px; }
  /* 16px, so iOS does not zoom the page the moment the picker takes focus. */
  #rm-to { font-size: 16px; min-height: 40px; }
  /* Access labels: put the label on its own line and let the chips wrap full
     width, so a member with many labels never breaks the card. */
  .team-table td[data-label="Access labels"] {
    flex-direction: column; align-items: stretch; text-align: start; gap: 6px; max-width: none;
  }
}

/* ---------- very small phones (≤360px, covers 320) ---------- */
@media (max-width: 360px) {
  #topbar { padding: 0 8px; }
  .brand { font-size: 14px; }
  .plan-badge { display: none; }         /* reclaim header space (plan lives in Settings) */
  .topbar-right { gap: 4px; }
  /* `width` is inert here — flex-basis:0 from the bar rules above decides the
     size — so the fixed 46px was never doing anything. What actually keeps eight
     icons legible at 320px is dropping the cap and the glyph. */
  #rail { padding: 0 2px; }
  .rail-lbl { font-size: 10px; }
  .avatar { width: 34px; height: 34px; flex: 0 0 34px; font-size: 12px; }
  .chat-item { padding: 9px 10px; gap: 8px; }
  .chat-name { font-size: 13.5px; }
  .chat-preview { font-size: 12px; }
  .msg { max-width: 92%; }
  .msgs { padding: 10px; }
  .conv-head { gap: 6px; padding: 0 4px; }
  .conv-head-right .ai-btn { padding: 5px 6px; }
}

.empty { display: grid; place-items: center; height: 100%; color: var(--ink-soft); text-align: center; }
.toast {
  position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
  background: var(--ink); color: #fff; padding: 9px 18px; border-radius: 999px;
  font-size: 13px; z-index: 100; box-shadow: 0 6px 18px rgba(0,0,0,0.25);
}
:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---------- sync progress feedback ---------- */
.sync-banner {
  padding: 8px 12px; font-size: 12.5px; color: var(--ink-soft);
  background: var(--accent-soft); border-bottom: 1px solid var(--line);
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.sync-badge {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11px; font-weight: 600; padding: 2px 9px; border-radius: 999px;
  white-space: nowrap;
}
.sync-badge.queued { background: #eef2f1; color: var(--ink-soft); }
.sync-badge.running { background: var(--accent-soft); color: var(--accent); }
.sync-badge.done { background: var(--accent-soft); color: var(--accent); }
.sync-badge.failed { background: #fdeaea; color: var(--danger); }
.sync-bar {
  display: inline-block; width: 46px; height: 4px; border-radius: 999px;
  background: rgba(15, 122, 84, 0.18); overflow: hidden;
}
.sync-bar.wide { width: 90px; }
.sync-bar > span {
  display: block; height: 100%; background: var(--accent);
  transition: width 0.3s ease;
}

/* ---------- delivery / read receipts ---------- */
.ack { margin-inline-start: 4px; }

/* The marks are an SVG path, so weight comes from stroke-width rather than
   whatever hairline the system font happens to draw for U+2713. */
.tick { display: inline-flex; align-items: center; color: var(--tick-grey); }
.tick svg { display: block; }

/* Read stays in WhatsApp's blue family, but darker than their #53bdeb: their
   ticks sit inside a green bubble, ours sit on the page under it, and that
   sky blue drops to about 2:1 against a light ground — the exact complaint
   this change answers. This holds ~4.3:1 while still reading as "the blue
   one". */
.tick.read { color: var(--tick-read); }
.tick.pending { opacity: 0.55; font-size: 11px; }
/* Never reached WhatsApp. Deliberately not the receipt grey — this is not a
   quieter version of "sent", it is the opposite of it. */
.tick.failed { color: #c0392b; }

/* ---------- empty conversation state ---------- */
.chat-empty {
  margin: auto; max-width: 420px; text-align: center;
  display: flex; flex-direction: column; gap: 10px; align-items: center;
  color: var(--ink-soft); padding: 30px 20px;
}
.chat-empty-title { font-weight: 700; color: var(--ink); font-size: 15px; }
.chat-empty-body { font-size: 13px; line-height: 1.55; }
/* The syncing variant of the empty pane. A thin determinate bar rather than
   a spinner alone: "still working" is the message, and "how far" is the part
   that stops somebody reloading to check. */
.chat-empty-bar { width: 200px; height: 4px; border-radius: 2px;
  background: var(--line); overflow: hidden; margin: 2px auto 0; }
.chat-empty-bar > span { display: block; height: 100%; background: var(--accent);
  transition: width .4s ease; }
.chat-empty-note { font-size: 11.5px; color: var(--ink-soft); }

/* ---------- load older messages ---------- */
.load-older { display: flex; justify-content: center; padding: 4px 0 10px; }

/* ---------- media in the bubble ---------- */
/* The bubble is white-space: pre-wrap for text; media markup must not inherit
   that or the tag indentation shows up as blank lines. */
.bubble.has-media { padding: 5px 5px 7px; }
.bubble .media { white-space: normal; display: block; }
.bubble.has-media .bubble-text { display: block; padding: 5px 7px 0; }

.media-img, .media-video { display: block; max-width: min(300px, 60vw); }
.media-img { margin: 0; }
.media-img img {
  display: block; max-width: 100%; max-height: 340px;
  border-radius: 7px; cursor: zoom-in; background: var(--ground);
}
.media-img.sticker { max-width: 150px; }
.media-img.sticker img { max-height: 150px; background: none; }
.media-video video { display: block; max-width: 100%; max-height: 340px; border-radius: 7px; background: #000; }

.media-audio { display: flex; flex-direction: column; gap: 4px; min-width: min(260px, 62vw); }
.media-audio-label { font-size: 11.5px; font-weight: 600; color: var(--ink-soft); }
.media-audio audio { width: min(270px, 62vw); height: 34px; display: block; }

.media-doc {
  display: flex; align-items: center; gap: 10px; min-width: min(240px, 66vw);
  background: rgba(30, 43, 38, 0.04); border-radius: 8px; padding: 8px 10px;
}
.msg.out .media-doc { background: rgba(15, 122, 84, 0.08); }
.doc-ico { font-size: 22px; line-height: 1; flex: 0 0 auto; }
.doc-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.doc-name { font-weight: 600; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.doc-sub { font-size: 11px; color: var(--ink-soft); }
.doc-dl { flex: 0 0 auto; }
/* A card the browser can actually render is a button in all but name: it says
   so on hover and takes a focus ring, because it is keyboard-reachable. One the
   browser cannot render (a .docx) keeps the plain card and its download arrow,
   rather than inviting a click that would open a blank frame. */
.media-doc.viewable { cursor: pointer; }
.media-doc.viewable:hover { background: rgba(15, 122, 84, 0.1); }
.media-doc.viewable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.media-doc.loading { opacity: .6; pointer-events: none; }
/* Why a file was held back, under the button that offers it. Quieter than the
   action itself: the size is the decision, the reason is the context for it. */
.media-load-btn { display: flex; flex-direction: column; align-items: center; gap: 2px; line-height: 1.3; }
.media-why { font-size: 10.5px; opacity: .72; font-weight: 400; }

.media-skel {
  display: grid; place-items: center; min-height: 120px; min-width: min(220px, 55vw);
  background: var(--ground); border: 1px solid var(--line); border-radius: 7px; font-size: 13px;
}
.media-skel.thin { min-height: 34px; }
.media-note {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 12.5px; color: var(--ink-soft);
  background: var(--ground); border: 1px solid var(--line);
  border-radius: 7px; padding: 7px 9px; min-width: min(220px, 62vw);
}
.media-note.failed { background: #fdf6ec; border-color: #f0e0c4; }
.media-note-txt { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.media-note .media-ico { font-size: 18px; }
.media-sub { font-size: 11px; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.media-note.pending { background: var(--panel); }
.media-pulse { animation: media-pulse 1.4s ease-in-out infinite; }
@keyframes media-pulse { 0%, 100% { opacity: 0.45; } 50% { opacity: 1; } }
.media-load-btn {
  border: 1px solid var(--line); background: var(--panel); color: var(--ink);
  border-radius: 7px; padding: 9px 12px; font-size: 12.5px; font-weight: 600;
}
.media-video > .media-load-btn, .media-audio > .media-load-btn { width: 100%; }
.media-load-btn:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }

/* ---------- lightbox ---------- */
/* The mail attachment viewer. Dimmed rather than black: the thread behind it
   is the context you are reading the file in, and a PDF page is white, so a
   full blackout makes the page look like it is floating in nothing.

   A bar across the TOP, not the bottom like the image lightbox — a PDF viewer
   puts its own toolbar at the top of the frame, and two bars fighting over the
   same edge is worse than the asymmetry. */
.docview {
  position: fixed; inset: 0; z-index: 130; background: rgba(12, 20, 17, 0.62);
  display: grid; place-items: center;
  /* More room under the panel than over it. A document sat hard against the
     bottom edge reads as cut off — you keep looking for the scrollbar that
     would show you the rest — and the eye tolerates a heavier base than top
     anyway. Clamped so a short laptop screen does not spend a tenth of itself
     on margin, and a tall one does not float the panel in the middle. */
  padding: 3vh 3vw clamp(28px, 8vh, 76px);
}
/* A panel with the page showing around it, not a full blackout. The dim is the
   point: it says the thread is still there and you are on top of it, which is
   what makes Escape feel like stepping back rather than navigating. */
.docview-panel {
  width: min(1100px, 100%); height: 100%;
  display: flex; flex-direction: column; overflow: hidden;
  border-radius: 12px; box-shadow: 0 24px 64px rgba(0, 0, 0, .45);
}
.docview-bar {
  flex: none; display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; background: rgba(12, 20, 17, 0.95); color: #fff;
}
.docview-name {
  font-size: 13.5px; font-weight: 600; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.docview-size { font-size: 12px; opacity: .75; flex: none; }
.docview-acts { margin-inline-start: auto; display: flex; align-items: center; gap: 8px; flex: none; }
.docview-acts .btn { text-decoration: none; color: var(--ink); background: #fff; border-color: #fff; }
.docview-close {
  width: 32px; height: 32px; border-radius: 50%; cursor: pointer;
  border: 1px solid rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.1); color: #fff; font-size: 14px; line-height: 1;
}
.docview-close:hover { background: rgba(255, 255, 255, 0.22); }
/* The frame fills what is left. A PDF wants the height far more than the width,
   so it takes the whole area rather than sitting in a centred card. */
.docview-frame { flex: 1; width: 100%; border: 0; background: #fff; }
.docview-img { flex: 1; min-height: 0; object-fit: contain; background: #101614; padding: 12px; }
@media (pointer: coarse) { .docview-close { width: 44px; height: 44px; } }

/* ON A PHONE THE PANEL IS THE SCREEN.
 *
 * The inset and the dim are a desktop idea: they say "the thread is still
 * behind this". On a 390px screen there is no behind — the margin is just less
 * document, and a PDF is the one thing that needs every pixel of a small screen.
 * So it goes full-bleed, square, and the bar becomes the only chrome.
 *
 * Which makes the bar the way out, so it gets the safe-area inset: on a notched
 * iPhone a bar at inset:0 puts the close button under the status bar, and the
 * one control that dismisses a full-screen overlay must never be the one the
 * hardware covers. */
@media (max-width: 640px) {
  .docview { padding: 0; }
  .docview-panel { width: 100%; height: 100%; border-radius: 0; box-shadow: none; }
  .docview-bar {
    padding: 10px 12px;
    padding-top: calc(10px + env(safe-area-inset-top, 0px));
    padding-inline-start: calc(12px + env(safe-area-inset-left, 0px));
    padding-inline-end: calc(12px + env(safe-area-inset-right, 0px));
    gap: 8px;
  }
  .docview-name { font-size: 14px; }
  /* The size is the first thing to go: the filename is what you came to check,
     and on a narrow bar the two of them together leave neither readable. */
  .docview-size { display: none; }
  .docview-img { padding: 0; }
  /* Home-indicator gutter, so the last line of a page is not under the bar the
     phone draws over everything. */
  .docview-frame { padding-bottom: env(safe-area-inset-bottom, 0px); background: #fff; }
}

.lightbox {
  position: fixed; inset: 0; z-index: 120; background: rgba(12, 20, 17, 0.88);
  display: grid; place-items: center; padding: 40px 20px 74px;
}
.lightbox img { max-width: 92vw; max-height: 82vh; object-fit: contain; border-radius: 6px; }
.lightbox-close {
  position: absolute; top: 14px; inset-inline-end: 18px; width: 36px; height: 36px;
  border-radius: 50%; border: 1px solid rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.1); color: #fff; font-size: 15px; line-height: 1;
}
.lightbox-close:hover { background: rgba(255, 255, 255, 0.22); }
.lightbox-bar {
  position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 12px; max-width: 92vw;
  background: rgba(0, 0, 0, 0.45); color: #fff; font-size: 12.5px;
  padding: 8px 12px; border-radius: 999px;
}
.lightbox-bar > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lightbox-bar .btn { text-decoration: none; color: var(--ink); background: #fff; border-color: #fff; }

/* ---------- attachment menu (WhatsApp-Desktop style) ---------- */
.attach-wrap { position: relative; flex: 0 0 auto; align-self: flex-end; }
.attach-btn {
  width: 40px; height: 40px; border-radius: 50%; border: 1px solid var(--line);
  background: var(--ground); color: var(--accent); font-size: 20px; font-weight: 700;
  display: grid; place-items: center; line-height: 1;
}
.attach-btn:hover { background: var(--accent-soft); border-color: var(--accent); }
.attach-menu {
  position: absolute; bottom: calc(100% + 8px); inset-inline-start: 0; width: 230px; z-index: 70;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 10px 28px rgba(30, 43, 38, 0.18); padding: 6px;
}
.attach-menu button {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: start;
  background: none; border: none; padding: 10px 12px; border-radius: 8px; font-size: 13.5px;
}
.attach-menu button:hover { background: var(--ground); }
.am-ico { font-size: 17px; width: 22px; text-align: center; }

/* ---------- upload preview row ---------- */
.attach-preview {
  display: flex; gap: 10px; align-items: flex-start; margin-bottom: 10px;
  background: var(--ground); border: 1px solid var(--line); border-radius: 10px; padding: 10px;
}
.ap-thumb { width: 54px; height: 54px; flex: 0 0 54px; object-fit: cover; border-radius: 7px; }
.ap-ico { width: 54px; height: 54px; flex: 0 0 54px; display: grid; place-items: center; font-size: 28px; background: var(--panel); border: 1px solid var(--line); border-radius: 7px; }
.ap-body { flex: 1; min-width: 0; }
.ap-name { font-weight: 700; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ap-sub { font-size: 11.5px; color: var(--ink-soft); }
.ap-caption { width: 100%; margin-top: 6px; padding: 6px 9px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); }
.ap-progress { height: 5px; margin-top: 7px; border-radius: 999px; background: rgba(15, 122, 84, 0.18); overflow: hidden; }
.ap-progress > span { display: block; height: 100%; width: 0; background: var(--accent); transition: width 0.15s linear; }
.ap-actions { display: flex; flex-direction: column; gap: 6px; flex: 0 0 auto; }

/* ---------- drag & drop onto the conversation ---------- */
#conv { position: relative; }
#conv.drag-over::after {
  content: "Drop to attach"; position: absolute; inset: 8px; z-index: 10;
  border: 2px dashed var(--accent); border-radius: 12px; background: rgba(15, 122, 84, 0.08);
  display: grid; place-items: center; font-weight: 700; color: var(--accent);
  pointer-events: none;
}

@media (max-width: 860px) {
  .media-img, .media-video { max-width: 68vw; }
  .attach-preview { flex-wrap: wrap; }
  .ap-actions { flex-direction: row; width: 100%; justify-content: flex-end; }
  .attach-menu { width: 208px; }
}

/* ---------- onboarding health (per-device sync stats) ---------- */
.sect-h { margin: 26px 0 12px; font-size: 15px; }
.dev-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 14px; }
.dev-card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px; }
.dev-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.dev-name { font-weight: 700; }
.pstatus.ok { color: var(--accent); }
.pstatus.off { color: var(--ink-soft); }
.dev-num { font-size: 12px; color: var(--ink-soft); margin-bottom: 10px; font-variant-numeric: tabular-nums; }
.dev-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px 6px; margin-bottom: 10px; }
.dev-grid div { display: flex; flex-direction: column; }
.dev-grid b { font-size: 17px; font-variant-numeric: tabular-nums; }
.dev-grid span { font-size: 11px; color: var(--ink-soft); }
.dev-hist, .dev-flow { font-size: 12px; color: var(--ink-soft); margin-bottom: 4px; }
.dev-days { margin-inline-start: 6px; color: var(--accent); font-weight: 600; }
.dev-media { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.dev-media.muted, .muted { color: var(--ink-soft); font-size: 12px; }
.mchip { font-size: 10.5px; font-weight: 600; padding: 2px 8px; border-radius: 999px; background: var(--accent-soft); color: var(--accent); }
.mchip.warn { background: #fdf0e0; color: var(--warn); }
/* Expired media is a statement of fact, not something to act on — keep it quiet. */
.mchip.muted { background: var(--surface-2, #eceff3); color: var(--muted, #6b7280); }
.tbl-scroll { overflow-x: auto; }

/* ---------- group table controls ---------- */
.grp-controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 12px; }
.grp-search { flex: 1 1 220px; min-width: 180px; }
.grp-controls select { padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); font-size: 12.5px; }
.grp-pager { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.pager-btns { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.pager-now { font-size: 12px; color: var(--ink-soft); padding: 0 4px; font-variant-numeric: tabular-nums; }
.btn.sm[disabled] { opacity: 0.4; cursor: default; }

/* ---------- voice notes ---------- */
.composer-side { display: flex; flex-direction: row; align-items: center; gap: 8px; }
.voice-btn {
  width: 38px; height: 38px; flex: 0 0 38px;
  border: 1px solid var(--line); border-radius: 50%;
  background: var(--panel); font-size: 17px; line-height: 1;
  cursor: pointer; transition: background 0.15s ease, border-color 0.15s ease;
}
.voice-btn:hover { background: var(--ground); border-color: var(--accent); }
.voice-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.voice-bar {
  display: flex; align-items: center; gap: 10px;
  margin-top: 8px; padding: 8px 12px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
}
.voice-bar.hidden { display: none; }
.voice-label { font-size: 13px; color: var(--ink-soft); }
/* Tabular figures so the timer does not jitter as the digits change. */
.voice-time { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }
.voice-spacer { flex: 1; }
.voice-bar button {
  border: 1px solid var(--line); border-radius: 8px; padding: 5px 12px;
  background: var(--panel); font: inherit; font-size: 13px; cursor: pointer;
}
.voice-send { background: var(--accent) !important; border-color: var(--accent) !important; color: #fff; font-weight: 600; }
.voice-cancel { color: var(--ink-soft); }

.voice-dot {
  width: 9px; height: 9px; flex: 0 0 9px; border-radius: 50%; background: #c0392b;
  animation: voice-pulse 1.2s ease-in-out infinite;
}
@keyframes voice-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
/* A blinking dot is decoration, not information — the word "Recording" and the
   running timer already carry the state. */
@media (prefers-reduced-motion: reduce) { .voice-dot { animation: none; } }

/* ================= Mail module ================= */
/* CONTAINERS, NOT MEDIA QUERIES.
   Both panes are resized by the splitter between them, so how much room a
   toolbar has has nothing to do with the size of the window: drag the divider
   right and the reader is cramped on a 27" monitor, which is exactly when its
   buttons need to shed their labels. A media query cannot see that. These two
   declare themselves query containers so their own toolbars can measure the
   space they are actually in. */
.mail-list-pane { flex: 0 0 360px; display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--panel);
  container: maillist / inline-size; }
/* The border moved onto .mail-splitter, which draws the same hairline but can
   be grabbed. Leaving it here too would paint two lines side by side. */
.mail-view-pane { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; background: var(--ground);
  container: mailview / inline-size; }
.mail-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 8px; flex: none; gap: 8px; }
.mail-head h2 { margin: 0; font-size: 18px; }
.mail-head-actions { display: flex; align-items: center; gap: 6px; flex: none; }
.mail-hb { display: inline-flex; align-items: center; gap: 6px; }
.mail-hb-ico { line-height: 1; }

/* THE LABELS COME OFF WHEN THE PANE CANNOT HOLD THEM.
   Sync + Select + Mailbox + Compose spelled out want ~330px next to the "Mail"
   heading — more than the list pane's own default width, which is why these
   were already colliding before anybody touched the splitter. Below this, they
   are square icon buttons and the title attribute carries the word. */
@container maillist (max-width: 430px) {
  .mail-hb-txt { display: none; }
  .mail-hb { width: 32px; padding: 0; justify-content: center; }
  .mail-hb-ico { font-size: 14px; }
}
/* horizontal filter strip — stays one row no matter how many mailboxes.
   Wrapped in .mail-filter-wrap so left/right arrows can flank the scroll area;
   the arrows are the scroll affordance for mouse (Windows) users, where the
   scrollbar is hidden and there is no trackpad swipe. */
.mail-filter-wrap { position: relative; flex: none; }
.mail-filter { display: flex; flex-wrap: nowrap; gap: 6px; padding: 0 12px 8px; overflow-x: auto; flex: none; scrollbar-width: none; scroll-behavior: smooth; }
.mail-filter::-webkit-scrollbar { display: none; }
/* fade cue at each end, shown only when there is more to scroll that way */
.mail-filter-wrap::before, .mail-filter-wrap::after { content: ""; position: absolute; top: 0; bottom: 8px; width: 28px; pointer-events: none; opacity: 0; transition: opacity .15s; z-index: 1; }
.mail-filter-wrap::before { inset-inline-start: 0; background: linear-gradient(to right, var(--panel), transparent); }
.mail-filter-wrap::after { inset-inline-end: 0; background: linear-gradient(to left, var(--panel), transparent); }
.mail-filter-wrap.can-left::before { opacity: 1; }
.mail-filter-wrap.can-right::after { opacity: 1; }
.mail-filter-arrow { position: absolute; top: 50%; transform: translateY(calc(-50% - 4px)); z-index: 2; width: 24px; height: 24px; display: none; align-items: center; justify-content: center; padding: 0; border-radius: 999px; border: 1px solid var(--line); background: var(--panel); color: var(--ink); cursor: pointer; box-shadow: 0 1px 4px rgba(0,0,0,.12); font-size: 13px; line-height: 1; }
.mail-filter-arrow:hover { background: var(--ground); }
.mail-filter-arrow.left { inset-inline-start: 2px; }
.mail-filter-arrow.right { inset-inline-end: 2px; }
.mail-filter-wrap.can-left .mail-filter-arrow.left { display: flex; }
.mail-filter-wrap.can-right .mail-filter-arrow.right { display: flex; }
.mail-fchip { flex: none; font-size: 12px; padding: 4px 10px; border-radius: 999px; border: 1px solid var(--line); background: var(--ground); color: var(--ink-soft); cursor: pointer; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mail-fchip.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); font-weight: 600; }
.mail-search { padding: 0 12px 8px; flex: none; }
.mail-search .search-input { width: 100%; }
.mail-items { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.mail-row { display: flex; gap: 11px; padding: 11px 14px; border-bottom: 1px solid var(--line); cursor: pointer; position: relative; }
.mail-row:hover { background: var(--ground); }
.mail-row.active { background: var(--accent-soft); }
.mail-row.unread::before { content: ''; position: absolute; inset-inline-start: 0; top: 0; bottom: 0; width: 3px; background: var(--accent); }
.mail-avatar { flex: none; width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center;
  color: #fff; font-weight: 700; font-size: 14px; margin-top: 2px; }
.mail-row-body { flex: 1; min-width: 0; }
.mail-row-top { display: flex; gap: 7px; align-items: baseline; }
.mail-row-top .mail-from { flex: 1 1 auto; }
.mail-from { font-size: 13.5px; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mail-row.unread .mail-from, .mail-row.unread .mail-subject { font-weight: 800; }
.mail-time { font-size: 11.5px; color: var(--ink-soft); flex: none; }
.mail-subject { font-size: 13px; color: var(--ink); margin: 1px 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mail-snippet { font-size: 12.5px; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mail-row-meta { display: flex; align-items: center; gap: 6px; margin-top: 4px; }

/* The day heading. Sticky, because the whole reason it exists is to say what
   "13:58" means, and a heading that has scrolled away answers nothing. */
.mail-daygroup {
  position: sticky; top: 0; z-index: 2;
  padding: 8px 14px 4px; background: var(--panel);
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-soft);
}

/* Which mailbox this landed in, when more than one does. Nine pixels instead of
   a chip carrying the same address on every row — see mailRowHtml for why. */
/* A RING, NOT A DISC — labels are already discs.
   Both markers are small coloured circles a few pixels apart, and the palette
   they draw from is the same one, so a filled mailbox dot beside a filled label
   dot is two identical marks meaning two unrelated things. Hollow reads as
   "where this came from"; solid reads as "what it is tagged". */
.mail-boxdot { width: 9px; height: 9px; border-radius: 50%; flex: none;
  background: transparent !important; border: 2.5px solid currentColor; }
.mail-clip { font-size: 11px; color: var(--ink-soft); margin-inline-start: auto; flex: none; }

/* Archive and read/unread, on hover.
   Absolutely positioned so they cost the row no width when idle, and opaque so
   they sit over the snippet rather than reflowing it — a row that changes shape
   under the cursor is a row you misclick. */
.mail-rowact {
  display: none; position: absolute; inset-inline-end: 10px; top: 8px; gap: 2px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 2px;
  box-shadow: 0 1px 3px rgba(20,32,28,.08);
}
.mail-row:hover .mail-rowact { display: flex; }
.mail-rowact button {
  width: 27px; height: 27px; border: 0; border-radius: 6px; background: none;
  font-size: 13px; color: var(--ink-soft); cursor: pointer; line-height: 1;
}
.mail-rowact button:hover { background: var(--accent-soft); color: var(--accent); }
/* A touch screen has no hover, so the row would keep its actions permanently
   secret. They are always out below the breakpoint — see the mobile block. */
/* visible, subtle scrollbars so it's obvious the list / thread scroll */
.mail-items::-webkit-scrollbar, .mail-messages::-webkit-scrollbar { width: 10px; }
.mail-items::-webkit-scrollbar-thumb, .mail-messages::-webkit-scrollbar-thumb { background: #c4d2cc; border: 3px solid transparent; background-clip: padding-box; border-radius: 999px; }
.mail-items:hover::-webkit-scrollbar-thumb, .mail-messages:hover::-webkit-scrollbar-thumb { background: #a9bcb3; background-clip: padding-box; }
.mail-items, .mail-messages { scrollbar-width: thin; }
.mail-chip { font-size: 11px; padding: 1px 8px; border-radius: 999px; background: var(--ground); border: 1px solid var(--line); color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 200px; }
.mail-chip-gmail { background: #fdeeee; border-color: #f5c9c9; color: #b3261e; }
.mail-chip-zoho { background: #fff4e5; border-color: #f3d9ac; color: #a15c00; }
.mail-lchip { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.mail-empty, .mbx-empty { padding: 18px 16px; color: var(--ink-soft); font-size: 13px; }
.mail-connect-empty { padding: 40px 20px; text-align: center; color: var(--ink-soft); display: flex; flex-direction: column; align-items: center; gap: 10px; }
.mail-connect-emoji { font-size: 40px; }
.mail-connect-hint { font-size: 12px; }

/* thread view */
.mail-thread-head { display: flex; align-items: center; gap: 10px; padding: 12px 18px; border-bottom: 1px solid var(--line); background: var(--panel); }
/* The subject takes the row and yields it: the deal chip beside it is short and
   fixed, so the long, wrapping thing is the one that has to be allowed to shrink. */
.mail-thread-title { flex: 1 1 auto; min-width: 0; }
.mail-back { display: none; }
/* One line, truncated. A subject that wraps to three lines makes the header
   taller than the first message, and the part that identifies a thread is the
   front of it — the tail is "... Re: Fwd: Re:". The full text is in the tooltip. */
.mail-thread-subject { font-size: 16px; font-weight: 700; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mail-thread-sub { margin-top: 2px; }
.mail-messages { flex: 1; min-height: 0; overflow-y: auto; padding: 16px 18px 28px; display: flex; flex-direction: column; gap: 14px; }
/* the thread list column must scroll on its own too */
.mail-list-pane { min-height: 0; }
.mail-items { flex: 1; min-height: 0; overflow-y: auto; }
/* WHY flex: none, AND WHY IT IS THE WHOLE BUG.
   .mail-messages is a column flex container, so every card is a flex item with
   flex-shrink: 1. An item's automatic minimum size normally stops it shrinking
   below its content — EXCEPT when overflow is not visible, and these cards clip
   their iframe corners with overflow: hidden. So min-height resolved to 0, ten
   open messages were squeezed into the pane's height instead of overflowing it,
   each one clipped to a stub, and the pane never scrolled because nothing ever
   overflowed. Cards must keep their own height and let the PANE scroll. */
.mail-msg { flex: none; background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  overflow: hidden; box-shadow: 0 1px 2px rgba(16, 40, 34, .05); }
/* Our own replies are marked by a rail, not by a wash of colour over half the
   thread — a tinted card next to a white one reads as "unread vs read", which is
   not what direction means. The rail says who wrote it without recolouring it. */
.mail-msg.out { border-color: #cfe3da; box-shadow: inset 3px 0 0 var(--accent), 0 1px 2px rgba(16, 40, 34, .05); }
.mail-msg.out .mail-msg-head { background: #f4faf7; }
.mail-msg-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; padding: 10px 14px;
  border-bottom: 1px solid var(--ground); cursor: pointer; }
.mail-msg-head:hover { background: var(--ground); }
.mail-msg.out .mail-msg-head:hover { background: #ecf6f1; }
/* A sender's name shrinks before it wraps: on a phone this row also carries the
   time and three reply buttons, and a long name that wraps pushes them onto a
   line of their own. */
.mail-msg-from { font-weight: 700; font-size: 13.5px; color: var(--ink);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mail-msg-meta { font-size: 12px; color: var(--ink-soft); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mail-msg-time { font-size: 12px; color: var(--ink-soft); flex: none; }
.mail-body { width: 100%; border: none; display: block; background: #fff; min-height: 40px;
  opacity: 0; transition: opacity .18s ease, height .16s ease; }
.mail-body.ready { opacity: 1; }
.mail-msg:has(.mail-body:not(.ready)) { background: linear-gradient(100deg, var(--panel) 30%, var(--ground) 50%, var(--panel) 70%); }
.mail-quote-toggle { margin: 0 14px 8px; background: var(--ground); border: 1px solid var(--line); border-radius: 6px; font-size: 12px; color: var(--ink-soft); padding: 3px 10px; cursor: pointer; }
.mail-quote-toggle.hidden { display: none; }
/* ----- the compose window -----
   Fixed to the viewport, not to the mail pane: it has to survive going to look
   something up in another thread, which is the errand people run mid-email.
   Bottom-right is where every mail client puts it, and the one place that does
   not cover a list or a reading pane. */
.mail-compose {
  position: fixed; inset-inline-end: 22px; bottom: 0; z-index: 70;
  width: 560px; max-width: calc(100vw - 44px);
  display: flex; flex-direction: column;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px 12px 0 0; box-shadow: 0 -2px 28px rgba(20,32,28,.18);
}
.mail-compose-head {
  display: flex; align-items: center; gap: 6px; padding: 9px 10px 9px 14px;
  background: var(--ink); color: #fff; border-radius: 11px 11px 0 0;
}
.mail-compose-title { flex: 1 1 auto; font-size: 13px; font-weight: 650; }
.mail-compose-btn {
  width: 26px; height: 26px; border: 0; border-radius: 6px; background: none;
  color: #cfdcd7; font-size: 12px; cursor: pointer;
}
.mail-compose-btn:hover { background: rgba(255,255,255,.14); color: #fff; }
/* The phone's Send. Off on a pointer device, where the footer button is where
   the eye already goes — two Sends on one screen is a question, not a shortcut. */
.mail-compose-send-top { display: none; }
.mail-compose-body { padding: 10px 14px 14px; }
/* Minimised is the whole point of a window rather than a modal: park it, read
   the thing you needed, come back to what you had written. */
.mail-compose.is-min .mail-compose-body { display: none; }
.mail-compose .mrc-field { border-bottom: 1px solid var(--line); padding: 7px 2px; }
.mail-compose .mrc-toggles { padding: 5px 2px 2px; }
.mail-compose select.mrc-input { cursor: pointer; }
.mail-compose-text {
  width: 100%; margin-top: 10px; resize: vertical; min-height: 170px;
  border: 1px solid var(--line); border-radius: 10px; padding: 11px 13px;
  font: inherit; font-size: 14px; line-height: 1.55; background: var(--ground); color: var(--ink);
}
.mail-compose-text:focus { outline: none; border-color: var(--accent); background: var(--panel); }
/* Shown but not offered: the sender of a reply is the thread's mailbox. Styled
   as text rather than as a disabled input, because a greyed-out control invites
   people to hunt for what would enable it. */
.mrc-fixed { flex: 1 1 auto; min-width: 0; font-size: 13.5px; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A reply's subject: shown, because you should know which conversation you are
   about to add to, and not a text box, because it is not a choice here. */
.mrc-subject-fixed {
  padding: 8px 2px; border-bottom: 1px solid var(--line);
  font-size: 13px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The template menu inside this window needs no rules of its own: .mail-tpl-menu
   already opens upwards off .mail-tpl-wrap, which is what a control sitting at
   the bottom of the screen requires. Repeating them here would be a second
   definition to keep in step with the first. */
.mail-compose-actions { display: flex; align-items: center; gap: 10px; margin-top: 11px; }
/* The one thing a forward cannot carry, said before the send rather than after.
   Amber because it is a limit to work around, not a failure. */
.mail-compose-warn {
  margin-top: 10px; padding: 9px 12px; border-radius: 9px;
  background: #fdf2e7; border: 1px solid #f3d9ac; color: #7a5713;
  font-size: 12.5px; line-height: 1.5;
}

@media (max-width: 640px) {
  /* A 560px window on a 390px phone is a modal pretending otherwise. */
  .mail-compose { inset-inline-end: 0; inset-inline-start: 0; width: auto; max-width: none; border-radius: 0;
    top: 0; border: 0; }
  .mail-compose-head { border-radius: 0; }
  .mail-compose-body { flex: 1 1 auto; overflow-y: auto; }
  .mail-compose.is-min { top: auto; }
  /* Send moves into the top bar — see mailComposeHtml for why the footer is the
     wrong place on a phone (the keyboard sits on top of it). */
  .mail-compose-send-top {
    display: grid; place-items: center; flex: none;
    width: 44px; height: 36px; margin-inline-end: 2px;
    border: 0; border-radius: 8px; background: var(--accent); color: #fff;
    font-size: 15px; cursor: pointer;
  }
  .mail-compose-send-top:disabled { opacity: .5; }
  /* The body grows to fill the screen instead of a fixed 170px box floating in
     the middle of one — the message is the reason the screen is open. */
  .mail-compose-text { min-height: 40vh; }
}

/* Autosave, said quietly. Blank while typing rather than "Saving…", because a
   label that flickers on every keystroke is noise about something that is
   working. */
.mail-compose-saved { font-size: 11.5px; color: var(--accent); min-width: 42px; }

/* The shortcuts, printed where the mouse already is — a keyboard shortcut
   nobody can discover is a feature for the person who wrote it. */
.mail-keys { display: block; margin-top: 2px; font-size: 11px; color: var(--ink-soft); }
.mail-keys b {
  display: inline-block; min-width: 14px; padding: 0 3px; margin-inline-end: 1px;
  border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 4px;
  background: var(--ground); font: inherit; font-size: 10px; font-weight: 700; text-align: center;
}
/* The width-based twin of this rule was removed: it hid the keyboard hints from
   anybody with a narrow WINDOW, who has a keyboard and can use them. The rule
   that survives asks about the input — see @media (hover: none) below. */

/* Snooze presets, and the sentence that keeps somebody out of trouble. */
.mail-tb-note {
  padding: 7px 9px 8px; font-size: 11.5px; line-height: 1.45; color: var(--ink-soft);
  border-bottom: 1px solid var(--line); margin-bottom: 4px;
}
.mail-tb-note b { color: var(--warn); }

/* The template menu's rules live BELOW .mail-tb-menu — see there. */

/* ----- the reader's toolbar ----- */
.mail-thread-tools {
  display: flex; align-items: center; gap: 2px; flex-wrap: wrap;
  padding: 7px 14px; border-bottom: 1px solid var(--line); background: var(--panel);
}
.mail-tb {
  display: inline-flex; align-items: center; gap: 7px; height: 32px; padding: 0 10px;
  border: 0; border-radius: 8px; background: none; font: inherit; font-size: 13px;
  color: var(--ink-soft); cursor: pointer; white-space: nowrap;
}
.mail-tb:hover:not(:disabled) { background: var(--ground); color: var(--ink); }
.mail-tb:disabled { opacity: .45; cursor: default; }
.mail-tb-ico { font-size: 13px; line-height: 1; }

/* SEVEN VERBS DO NOT FIT A NARROW READER.
   Spelled out they want roughly 620px; drag the splitter right, or open this on
   a laptop beside the list, and they wrap onto a second row — a toolbar that
   changes height as you resize is worse than one that changes shape. Below the
   threshold each is a square icon and its title carries the verb. */
@container mailview (max-width: 620px) {
  .mail-tb-txt { display: none; }
  .mail-tb { width: 34px; padding: 0; justify-content: center; }
  .mail-tb-ico { font-size: 15px; }
  .mail-tb-sep { margin: 0 3px; }
  /* The count is the first thing to go: it is the least useful and the widest. */
  .mail-tb-count { display: none; }

  /* WHAT THE HIDDEN LABEL WAS CARRYING.
     Three of these say their own state in their text — "2 labels", the
     assignee's name, "Until Tue". Drop the text and the row stops showing that
     anything is set at all, so the state comes back as a dot. Only here: with
     the words on screen a dot would be repeating them. */
  .mail-tb.is-set { position: relative; color: var(--accent); }
  .mail-tb.is-set::after {
    content: ''; position: absolute; top: 5px; inset-inline-end: 5px;
    width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
  }

  /* The deal chip in the header row keeps its stage and its value — the two
     things you glance at — and drops the title, which the Contacts button
     opens in full anyway. */
  .mail-crm .mail-crm-title { display: none; }
}

/* On a phone-width reader the header row is a back arrow, a subject and this.
   Down to the stage and the way through to the record: the amount is not what
   anybody needs mid-triage, and advancing a deal a stage is not a thing done
   one-handed while clearing an inbox — it is done on the deal. */
@container mailview (max-width: 480px) {
  .mail-crm .mail-crm-val, .mail-crm #crm-mail-advance { display: none; }
}
.mail-tb-sep { width: 1px; height: 20px; background: var(--line); margin: 0 6px; flex: none; }
.mail-tb-grow { flex: 1 1 auto; }
.mail-tb-count { font-size: 11.5px; color: var(--ink-soft); flex: none; }
.mail-tb-wrap { position: relative; }
.mail-tb-menu {
  position: absolute; top: calc(100% + 4px); inset-inline-start: 0; z-index: 30; min-width: 190px;
  max-height: 260px; overflow-y: auto; padding: 5px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 8px 24px rgba(20,32,28,.14);
}
.mail-tb-opt {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: start;
  padding: 7px 9px; border: 0; border-radius: 7px; background: none;
  font: inherit; font-size: 13px; color: var(--ink); cursor: pointer;
}
.mail-tb-opt:hover { background: var(--ground); }
.mail-tb-opt.on { color: var(--accent); font-weight: 600; }
.mail-tb-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.mail-tb-empty { padding: 9px; font-size: 12.5px; color: var(--ink-soft); }

/* Templates — the same saved replies the WhatsApp composer uses, so a team
   keeps its wording in one place.
   ORDER IS THE FEATURE HERE, AND IT MUST STAY BELOW .mail-tb-menu.
   The button sits at the bottom of the compose window, so its menu has to open
   UPWARDS: bottom instead of top. Both selectors are a single class, so neither
   out-specifies the other and the later one simply wins. Declared above
   .mail-tb-menu (where it used to live) `top: auto` lost, the menu ended up
   with top AND bottom set against a ~28px button, and an absolutely positioned
   box that is over-constrained in both directions solves its auto height to
   28 - 32 - 34 = a negative number, clamped to zero. It rendered as a single
   hairline — a menu that opened as one thin line and showed nothing. */
.mail-tpl-wrap { position: relative; }
.mail-tpl-menu { bottom: calc(100% + 6px); top: auto; min-width: 260px; }
.mail-tpl-menu .mail-tb-opt { flex-direction: column; align-items: flex-start; gap: 1px; }
.mail-tpl-menu b { color: var(--accent); font-size: 12.5px; }
.mail-tpl-gist { font-size: 11.5px; color: var(--ink-soft); }

/* ----- a read message, folded to one line -----
   The point is that a fifteen-message thread stops being a wall. One line each,
   and the newest open, so what you clicked the thread for is what you see. */
.mail-msg-collapsed {
  flex: none;                 /* same shrink trap as .mail-msg above */
  display: flex; align-items: baseline; gap: 10px; width: 100%; text-align: start;
  padding: 10px 13px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
  font: inherit; font-size: 13px; color: var(--ink-soft); cursor: pointer;
  transition: background .12s ease, border-color .12s ease;
}
.mail-msg-collapsed:hover { border-color: #cfdcd7; background: var(--ground); }
.mail-msg-cwho { font-weight: 600; color: var(--ink); flex: none; max-width: 34%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mail-msg-cgist { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mail-msg-cclip, .mail-msg-ctime { flex: none; font-size: 11.5px; }

/* Per-message reply, on the message's own header. */
.mail-msg-act { display: flex; gap: 2px; flex: none; margin-inline-start: 2px; }
.mail-msg-act button {
  width: 28px; height: 28px; border: 0; border-radius: 7px; background: none;
  color: var(--ink-soft); font-size: 12px; cursor: pointer;
}
.mail-msg-act button:hover { background: var(--ground); color: var(--ink); }

/* ----- attachment cards -----
   These were text chips whose tooltip said the file is not stored — true, and
   invisible until hovered. Type, name and size is what tells somebody whether
   the PO they want is on this message, and the size doubles as the honest label
   when there is none: "in the mailbox". */
.mail-attachments { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 14px 12px; }
.mail-att {
  display: flex; align-items: center; gap: 9px; min-width: 186px; max-width: 260px;
  padding: 7px 11px; border: 1px solid var(--line); border-radius: 9px; background: var(--ground);
}
.mail-att-ico {
  width: 30px; height: 30px; flex: none; border-radius: 7px; display: grid; place-items: center;
  font-size: 10px; font-weight: 700; letter-spacing: .02em; background: var(--line); color: var(--ink-soft);
}
.mail-att-pdf .mail-att-ico { background: #fdecec; color: var(--danger); }
.mail-att-xls .mail-att-ico { background: var(--accent-soft); color: var(--accent); }
.mail-att-doc .mail-att-ico { background: #e4effc; color: var(--info); }
.mail-att-img .mail-att-ico { background: #f3e8fd; color: #7a3ec4; }
.mail-att-zip .mail-att-ico { background: #fdf2e7; color: var(--warn); }
.mail-att-body { min-width: 0; display: flex; flex-direction: column; }
.mail-att-name { font-size: 12.5px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mail-att-size { font-size: 11px; color: var(--ink-soft); }

/* Open / Save. Always present rather than revealed on hover: a card that grows
   a control when the pointer arrives is a control that does not exist on a
   touch screen, and mail is read on a phone as much as anywhere. The card grew
   to fit them rather than the buttons being squeezed in beside the name. */
.mail-att { min-width: 232px; }
.mail-att-acts { margin-inline-start: auto; display: flex; gap: 4px; flex: none; }
.mail-att-btn {
  font: inherit; font-size: 11.5px; font-weight: 600; line-height: 1;
  padding: 5px 8px; border-radius: 6px; cursor: pointer;
  border: 1px solid var(--line); background: var(--card); color: var(--ink-soft);
}
.mail-att-btn:hover { border-color: var(--accent); color: var(--accent); }
.mail-att-btn:disabled { opacity: .55; cursor: default; border-color: var(--line); color: var(--ink-soft); }
@media (pointer: coarse) {
  /* 44px targets, the same rule the rest of the app follows. */
  .mail-att-btn { padding: 11px 12px; }
  .mail-att { max-width: 100%; width: 100%; }
}

/* The bar under the reader: three doors, one row, always the same height.
   The composer it used to unfold into now floats in the corner (.mail-compose),
   so this cannot take height from the email above it any more. */
.mail-reply { border-top: 1px solid var(--line); background: var(--panel); flex: none; position: relative; z-index: 1; }
.mail-reply-triggers { display: flex; align-items: center; gap: 4px; padding: 8px 16px; }
.mail-reply-trigger { display: flex; align-items: center; gap: 9px; text-align: start;
  padding: 8px 12px; border-radius: 9px; background: none; border: none; cursor: pointer; }
.mail-reply-trigger:hover { background: var(--ground); }
.mrt-ico { display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent); font-size: 15px; flex: none; }
.mrt-text { font-size: 13.5px; font-weight: 600; color: var(--ink); }
.mrt-from { font-size: 12px; color: var(--ink-soft); margin-inline-start: auto; max-width: 45%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* To / Cc / Bcc. Laid out like the header block of a real mail composer: a
   fixed label column so the three rows line up, and a field that takes the rest
   of the width — the addresses are the part you read back before pressing Send,
   so they get the room. */
.mrc-field { display: flex; align-items: baseline; gap: 8px; padding: 7px 11px; border-bottom: 1px solid var(--line); }
.mrc-label { flex: 0 0 32px; font-size: 12px; font-weight: 600; color: var(--ink-soft); }
.mrc-input { flex: 1 1 auto; min-width: 0; border: 0; background: none; padding: 0;
  font: inherit; font-size: 13.5px; color: var(--ink); }
.mrc-input:focus { outline: none; }
.mrc-input::placeholder { color: var(--ink-soft); opacity: .65; }
.mrc-toggles { display: flex; align-items: center; gap: 6px; padding: 6px 11px 7px; }
.mrc-toggle { border: 0; background: none; padding: 2px 6px; border-radius: 6px; cursor: pointer;
  font: inherit; font-size: 12px; font-weight: 600; color: var(--accent); }
.mrc-toggle:hover { background: var(--accent-soft); }

/* A blind copy on our own sent message. Marked rather than blended in: the
   people who received this never saw it, and reading the line back later should
   make that obvious rather than leave it to be remembered. */
.mail-bcc { color: var(--warn); }
.btn.subtle { background: none; border-color: transparent; color: var(--ink-soft); }
.btn.subtle:hover { background: var(--ground); color: var(--ink); }

/* Settings → Mailboxes */
.mbx-rows { display: flex; flex-direction: column; gap: 8px; margin: 12px 0; }
.mbx-row { display: flex; align-items: center; gap: 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); padding: 12px 14px; }
.mbx-icon { font-size: 22px; }
.mbx-info { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.mbx-email { font-weight: 700; font-size: 14px; }
.mbx-sub { font-size: 12px; color: var(--ink-soft); }
.mbx-err { color: var(--danger); }
/* The @ badge, beside the unread count on a chat somebody tagged you in.
   Green like WhatsApp's, because it means the same thing there: this one is
   about YOU, not merely unread. */
.mention-dot {
  display: inline-grid; place-items: center;
  min-width: 18px; height: 18px; padding: 0 4px;
  border-radius: 9px; background: var(--accent); color: #fff;
  font-size: 11px; font-weight: 700; line-height: 1;
}
/* The history import runs for hours on a large mailbox, so its line sits
   quietly under the address rather than competing with the status. */
.mbx-backfill { color: var(--accent); }
.mbx-connect { display: flex; flex-wrap: wrap; gap: 10px; margin: 4px 0 14px; }
/* Zoho app-password connect form */
.mbx-pw-form { border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
  padding: 14px 16px; margin: 0 0 14px; display: flex; flex-direction: column; gap: 10px; max-width: 460px; }
.mbx-pw-hint { font-size: 12px; color: var(--ink-soft); margin: 0; line-height: 1.5; }
.mbx-pw-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px;
  background: var(--ground); border: 1px solid var(--line); border-radius: 4px; padding: 1px 5px; }
.mbx-pw-form label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; font-weight: 600; color: var(--ink-soft); }
.mbx-pw-form input { font-size: 14px; padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--ground); color: var(--ink); }
.mbx-pw-form input:focus { outline: none; border-color: var(--accent); }
.mbx-pw-adv { font-size: 12px; }
.mbx-pw-adv summary { cursor: pointer; color: var(--ink-soft); user-select: none; }
.mbx-pw-adv[open] { display: flex; flex-direction: column; gap: 10px; }
.mbx-pw-adv[open] summary { margin-bottom: 6px; }
.mbx-pw-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.mbx-pw-msg { font-size: 12px; color: var(--ink-soft); }
.mbx-pw-msg.mbx-pw-err { color: var(--danger); }

/* Connect-Zoho dialog */
.modal-sub { font-size: 13px; color: var(--ink-soft); margin: 2px 0 16px; line-height: 1.5; }
.zconnect-tabs { display: flex; gap: 8px; margin-bottom: 16px; }
.zconnect-tab { flex: 1; display: flex; align-items: center; justify-content: center; gap: 7px;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--ground);
  color: var(--ink-soft); font-size: 13px; font-weight: 600; }
.zconnect-tab.active { border-color: var(--accent); color: var(--ink); background: var(--panel);
  box-shadow: 0 0 0 1px var(--accent) inset; }
.zconnect-badge { font-size: 9.5px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
  background: var(--accent-soft); color: var(--accent); border-radius: 999px; padding: 2px 7px; }
.zconnect-steps { margin: 0 0 16px; padding-inline-start: 20px; display: flex; flex-direction: column; gap: 8px;
  font-size: 13px; color: var(--ink); line-height: 1.55; }
.zconnect-steps li { padding-inline-start: 3px; }
.zconnect-note { font-size: 12px; color: var(--ink-soft); background: var(--note);
  border-inline-start: 3px solid var(--warn); border-radius: 8px; padding: 10px 12px; margin: 0 0 16px; line-height: 1.5; }
/* Reuse the mailbox password-form field styles, but flat inside the modal. */
#modal .mbx-pw-form { border: none; padding: 0; margin: 0; max-width: none; gap: 12px; }

/* Mobile: stack the connect tabs, keep mailbox rows from overflowing, and use
   16px inputs so iOS doesn't zoom on focus. */
@media (max-width: 640px) {
  .zconnect-tabs { flex-direction: column; gap: 8px; }
  .zconnect-tab { justify-content: flex-start; }
  .mbx-pw-form input { font-size: 16px; }
  .mbx-row { flex-wrap: wrap; }
  .mbx-row .btn.sm { flex: 1 1 auto; }
  .mbx-info { flex: 1 1 100%; }
  .mbx-connect .btn { flex: 1 1 auto; }
}

/* ===== Backup (Google Drive): Settings → Backup, and the details card =====
   Built out of the mailbox row vocabulary above on purpose — both screens are
   "an account is attached here, and these are the things flowing through it". */
.bk-conn { display: flex; align-items: center; gap: 12px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--panel); padding: 12px 14px; margin: 4px 0 18px; }
.bk-gicon { font-size: 18px; line-height: 1; }
.bk-conn-main { flex: 1 1 auto; min-width: 0; }
/* "Connected as archive@example.com" — a sentence with a long unbreakable
   token in it. Wrapped rather than truncated: an ellipsis here hides the one
   piece of information the line exists to carry, and on a 320px screen it
   hides most of it. overflow-wrap lets the address itself break. */
.bk-conn-email { font-size: 13.5px; font-weight: 600; min-width: 0;
  overflow-wrap: anywhere; }
.bk-conn-sub { font-size: 12px; color: var(--ink-soft); margin-top: 3px; line-height: 1.45; }
.bk-sub { font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-soft); margin: 0 0 8px; }
.bk-rows { display: flex; flex-direction: column; gap: 8px; margin: 0 0 12px; }
.bk-row { display: flex; align-items: center; gap: 12px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--panel); padding: 10px 13px; }
.bk-row-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.bk-row-name { font-size: 13px; font-weight: 600; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.bk-row-sub { font-size: 11.5px; color: var(--ink-soft); line-height: 1.4; }

.bk-switch { display: flex; align-items: center; gap: 8px; font-size: 12.5px; cursor: pointer; }
.bk-switch input { flex: 0 0 auto; }
.bk-kinds { margin-top: 12px; }
/* Off means the filter still SHOWS — what would be backed up if it were on —
   but greys out, so switching on is not a second decision made blind. */
.bk-kinds.off { opacity: .5; }
.bk-kinds-head { font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-soft); margin-bottom: 7px; }
.bk-kindgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(104px, 1fr)); gap: 6px 10px; }
.bk-kind { display: flex; align-items: center; gap: 6px; font-size: 12.5px; min-width: 0; cursor: pointer; }
.bk-kind input { flex: 0 0 auto; }
/* Wrapped, never ellipsised. These are checkbox labels: a person has to be
   able to READ what they are switching on, and the German for "voice notes" is
   seventeen characters in a 104px column. The 44px rows below absorb a second
   line at no cost. */
.bk-kind span { min-width: 0; overflow-wrap: break-word; }
.bk-status { margin-top: 12px; font-size: 11.5px; color: var(--ink-soft); line-height: 1.45; }
.bk-need { margin-bottom: 10px; }

/* The progress bar. Rendered only while a backfill is actually moving — see
   backupBarHtml — so it never sits full beside "Up to date". */
.bk-bar { display: block; height: 4px; border-radius: 999px; background: var(--line);
  overflow: hidden; margin-top: 7px; }
.bk-bar-fill { display: block; height: 100%; border-radius: 999px; background: var(--accent);
  transition: width .4s ease; }
@media (prefers-reduced-motion: reduce) { .bk-bar-fill { transition: none; } }

/* A finger, not a mouse pointer. Native checkboxes render about 13px, which is
   a third of what a thumb hits reliably — and the kind filter is seven of them
   in two columns, so a near-miss switches the wrong one. Same 44px rule, and
   the same reasoning, as the mail picker and the tag options. */
@media (pointer: coarse) {
  .bk-switch { min-height: 44px; }
  .bk-kind { min-height: 44px; }
  .bk-switch input, .bk-kind input { width: 22px; height: 22px; }
  .bk-kindgrid { gap: 2px 10px; }
  .bk-conn .btn, .bk-row .btn.sm { min-height: 44px; }
}

/* 320px: the panel is the whole screen and the connect row has to stack rather
   than squeeze a button against an email address. */
@media (max-width: 640px) {
  .bk-conn { flex-wrap: wrap; }
  .bk-conn-main { flex: 1 1 100%; }
  .bk-conn .btn { flex: 1 1 auto; }
  .bk-row { flex-wrap: wrap; }
  .bk-row-main { flex: 1 1 100%; }
  .bk-row .btn.sm { flex: 1 1 auto; }
}
@media (max-width: 360px) {
  .bk-kindgrid { grid-template-columns: 1fr 1fr; }
}

/* mobile: one pane at a time. Mail is an app-like two-pane view (like the inbox),
   so override the generic "non-inbox view scrolls as a block" rule — it needs a
   flex column that fills the screen, not a scrolling block. */
@media (max-width: 640px) {
  body[data-view="mail"] #view { display: flex; overflow: hidden; }
  .mail-list-pane, .mail-view-pane { flex: 1 1 auto; min-width: 0; min-height: 0; }
  .mail-list-pane { border-inline-end: none; }
  body.mail-open .mail-list-pane { display: none; }
  body:not(.mail-open) .mail-view-pane { display: none; }
  .mail-back { display: inline-flex; }
  .mail-head { padding: 12px 14px 6px; }
  .mail-head h2 { font-size: 17px; }
  .mail-fchip { max-width: 140px; }
  /* NO HOVER ON A TOUCH SCREEN. Left as `display:none until :hover`, archive and
     mark-read would be permanently unreachable on the device where flicking down
     a list matters most. Always visible here, and given a thumb-sized target. */
  .mail-rowact { display: flex; position: static; border: 0; box-shadow: none; background: none;
    align-self: center; margin-inline-start: 2px; padding: 0; }
  .mail-rowact button { width: 38px; height: 38px; font-size: 15px; }
  .mail-row { padding-inline-end: 8px; }
  .mail-messages { padding: 12px 12px 24px; gap: 12px; }
  .mail-msg-head { padding: 9px 11px; }
  .mail-msg-meta { flex: 1 1 100%; order: 3; }   /* to/cc wraps to its own line */
  .mail-thread-subject { font-size: 15px; }

  /* ---- the two bars above a thread, on a phone ----
     Between them they were spending about 90px of a 640px screen before a single
     line of the email appeared: a two-line title block (subject over mailbox
     chip) and a toolbar padded for a mouse. The subject and the mailbox go on
     ONE line — they are both "which conversation is this", and side by side they
     say it in half the height — and both bars lose the padding that was there to
     keep a cursor from clipping their edges. */
  .mail-thread-head { padding: 7px 10px; gap: 6px; }
  .mail-thread-title { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
  .mail-thread-sub { margin-top: 0; flex: 0 1 auto; min-width: 0; overflow: hidden; }
  .mail-thread-sub .mail-chip { white-space: nowrap; }
  .mail-thread-tools { padding: 3px 6px; }

  /* ---- Reply / Reply all / Forward ----
     THE EMPTY SPACE WAS "from sales@example.com". It sat at the far end of this
     row on margin-left:auto, so three left-packed buttons were followed by a gap
     and then a line of text repeating what the header above already says. Gone,
     and the three verbs divide the width instead: equal thirds, centred, and
     tall enough to hit — which is the bottom bar Outlook and Gmail both put
     under a thread on a phone. */
  .mail-reply-triggers { gap: 2px; padding: 5px 6px; }
  /* No min-height here on purpose: the pointer:coarse block below already owns
     it at 44px, and two rules setting the same property on the same selector are
     decided by source order — which is the kind of thing that silently reverses
     the day somebody reorders the file. */
  .mail-reply-trigger { flex: 1 1 0; min-width: 0; justify-content: center;
    gap: 7px; padding: 6px 4px; }
  .mrt-from { display: none; }
  .mrt-ico { width: 24px; height: 24px; font-size: 13px; }
  .mrt-text { font-size: 12.5px; }
  /* the app top bar is redundant while reading a thread — reclaim the height */
  body[data-view="mail"].mail-open #topbar { display: none; }
}

/* Mail thread → tickets strip */
.mail-tickets { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 18px; border-bottom: 1px solid var(--line); background: var(--panel); }
.mail-ticket-chip { font-size: 12px; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--line); background: var(--ground); color: var(--ink); cursor: pointer; }
.mail-ticket-chip:hover { border-color: var(--accent); color: var(--accent); }
.mail-thread-head #mail-make-ticket { flex: none; }

/* The assign/label bar that used to live here is gone — both are buttons in the
   reader's toolbar now (see .mail-thread-tools above). This block redefined
   .mail-thread-tools AFTER that one, so leaving it would have silently won on
   specificity-by-order and put the old strip's layout back. */

/* ================= Onboarding checklist widget ================= */
#onboarding-widget { position: fixed; inset-inline-end: 20px; bottom: 20px; z-index: 80; display: none; }
#onboarding-widget.show { display: block; }
/* collapsed pill */
.ob-pill { display: inline-flex; align-items: center; gap: 9px; padding: 10px 16px 10px 11px; border: none;
  border-radius: 999px; background: var(--accent); color: #fff; font-weight: 600; font-size: 13.5px; cursor: pointer;
  box-shadow: 0 6px 20px rgba(15, 122, 84, 0.35); }
.ob-pill:hover { filter: brightness(1.05); }
.ob-pill-ring { display: grid; place-items: center; width: 26px; height: 26px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.22); font-size: 11.5px; font-weight: 700; }
/* expanded card */
.ob-card { width: 340px; max-width: calc(100vw - 40px); background: var(--panel); border: 1px solid var(--line);
  border-radius: 14px; box-shadow: 0 16px 44px rgba(20, 30, 26, 0.22); overflow: hidden; }
.ob-head { display: flex; align-items: center; justify-content: space-between; padding: 13px 15px; }
.ob-head-title { font-size: 14.5px; font-weight: 700; color: var(--ink); }
.ob-count { font-size: 12px; font-weight: 700; color: var(--accent); background: var(--accent-soft); border-radius: 999px; padding: 1px 8px; margin-inline-start: 4px; }
.ob-head-actions { display: flex; gap: 4px; }
.ob-icon { border: none; background: none; color: var(--ink-soft); font-size: 15px; cursor: pointer; padding: 2px 6px; border-radius: 6px; }
.ob-icon:hover { background: var(--ground); color: var(--ink); }
.ob-progress { height: 5px; background: var(--ground); margin: 0 15px 8px; border-radius: 999px; overflow: hidden; }
.ob-progress > span { display: block; height: 100%; background: var(--accent); transition: width .4s ease; }
.ob-items { max-height: 46vh; overflow-y: auto; padding: 4px 8px 10px; }
.ob-item { display: flex; align-items: center; gap: 10px; padding: 9px 8px; border-radius: 9px; }
.ob-item + .ob-item { border-top: 1px solid var(--ground); }
.ob-check { flex: none; width: 20px; height: 20px; border-radius: 50%; border: 1.5px solid var(--line);
  display: grid; place-items: center; font-size: 12px; color: #fff; }
.ob-item.done .ob-check { background: var(--accent); border-color: var(--accent); }
.ob-item-body { flex: 1; min-width: 0; }
.ob-item-title { font-size: 13px; font-weight: 600; color: var(--ink); }
.ob-item.done .ob-item-title { color: var(--ink-soft); text-decoration: line-through; }
.ob-item-desc { font-size: 11.5px; color: var(--ink-soft); margin-top: 1px; }
.ob-item.done .ob-item-desc { display: none; }
.ob-cta { flex: none; }
/* lead-in line steering to the gateway step */
.ob-lead { margin: 0 15px 8px; font-size: 12px; line-height: 1.5; color: var(--ink-soft); }
/* the current step (first incomplete): spotlighted so the order is obvious */
.ob-item.current { background: var(--accent-soft); border: 1px solid var(--accent); margin: 4px 6px; padding: 11px 10px; }
.ob-item.current .ob-item-title { color: var(--accent); font-weight: 700; }
.ob-item.current + .ob-item { border-top: none; }
.ob-start-here { display: inline-block; margin-inline-start: 7px; font-size: 9.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em;
  color: #fff; background: var(--accent); border-radius: 4px; padding: 1px 6px; vertical-align: middle; }
/* upcoming steps stay visible & clickable, just a touch quieter than the current one */
.ob-item:not(.current):not(.done) .ob-item-desc { color: var(--ink-soft); }
/* opt-in tour launcher */
.ob-tour { display: block; width: calc(100% - 24px); margin: 4px 12px 12px; padding: 8px; border: 1px dashed var(--line);
  border-radius: 9px; background: none; color: var(--ink-soft); font-size: 12.5px; font-weight: 600; cursor: pointer; }
.ob-tour:hover { border-color: var(--accent); color: var(--accent); }
.ob-menu { padding: 6px 10px 10px; border-top: 1px solid var(--ground); }
.ob-menu button { border: none; background: none; color: var(--danger); font-size: 12.5px; cursor: pointer; padding: 4px; }
.ob-menu button:hover { text-decoration: underline; }
/* confetti */
.ob-confetti { position: fixed; inset: 0; pointer-events: none; z-index: 200; overflow: hidden; }
.ob-confetti i { position: absolute; top: -12px; width: 8px; height: 12px; border-radius: 2px; animation: ob-fall 2.4s linear forwards; }
@keyframes ob-fall { to { transform: translateY(102vh) rotate(560deg); opacity: 0.2; } }

/* getting-started settings tab reuse */
.gs-progress { display: flex; align-items: center; gap: 12px; margin: 6px 0 14px; }
.gs-progress .ob-progress { flex: 1; margin: 0; height: 7px; }
.gs-count { font-size: 12.5px; color: var(--ink-soft); font-weight: 600; }
.gs-items { max-height: none; border: 1px solid var(--line); border-radius: 12px; }

/* ================= Coach marks ================= */
#coach-layer { position: fixed; inset: 0; z-index: 120; background: rgba(20, 30, 26, 0.45); }
#coach-layer.hidden { display: none; }
.coach-spot { position: absolute; border-radius: 10px; box-shadow: 0 0 0 3px var(--accent), 0 0 0 9999px rgba(20,30,26,0.45);
  background: transparent; transition: all .2s ease; }
.coach-pop { position: absolute; width: 280px; max-width: calc(100vw - 24px); background: var(--panel); border-radius: 12px;
  box-shadow: 0 16px 44px rgba(20, 30, 26, 0.3); padding: 14px 16px; }
.coach-title { font-size: 14px; font-weight: 700; color: var(--ink); margin-bottom: 4px; }
.coach-body { font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; margin-bottom: 10px; }
.coach-dots { display: flex; gap: 5px; margin-bottom: 11px; }
.coach-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--line); }
.coach-dot.on { background: var(--accent); }
.coach-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.coach-actions-r { display: flex; align-items: center; gap: 7px; }
.coach-skip { border: none; background: none; color: var(--ink-soft); font-size: 12.5px; cursor: pointer; }
.coach-skip:hover { color: var(--ink); text-decoration: underline; }

/* mobile: widget becomes a full-width bottom sheet-ish card */
@media (max-width: 640px) {
  #onboarding-widget { inset-inline-end: 10px; inset-inline-start: 10px; bottom: 66px; }  /* clear the bottom nav */
  #onboarding-widget.show { display: flex; justify-content: flex-end; }
  .ob-card { width: 100%; max-width: 100%; }
  .coach-pop { inset-inline-start: 12px !important; inset-inline-end: 12px; width: auto; }
}

/* onboarding item error + retry, and getting-started funnel */
.ob-item.has-err .ob-item-title { color: var(--danger); }
.ob-item-err { font-size: 11.5px; color: var(--danger); margin-top: 3px; line-height: 1.4; }
.gs-analytics { margin-top: 22px; border-top: 1px solid var(--line); padding-top: 14px; }
.gs-ana-h { font-size: 13px; margin: 0 0 8px; color: var(--ink); }
.gs-evs { display: flex; flex-direction: column; gap: 4px; max-width: 460px; }
.gs-ev { display: flex; justify-content: space-between; align-items: center; font-size: 12.5px;
  padding: 6px 10px; border: 1px solid var(--line); border-radius: 7px; background: var(--panel); color: var(--ink-soft); }
.gs-ev b { color: var(--ink); }
.gs-ev-empty { font-size: 12.5px; color: var(--ink-soft); padding: 6px 0; }
/* ================= Contacts module ================= */
.contacts-list-pane { flex: 0 0 380px; display: flex; flex-direction: column; min-width: 0; min-height: 0; border-inline-end: 1px solid var(--line); background: var(--panel); }
.contacts-detail-pane { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; background: var(--ground); }
.contacts-head { display: flex; align-items: baseline; gap: 10px; padding: 14px 16px 8px; flex: none; }
.contacts-head h2 { margin: 0; font-size: 18px; }
.contacts-count { font-size: 12px; color: var(--ink-soft); }
.contacts-search { padding: 0 12px 8px; flex: none; }
.contacts-search .search-input { width: 100%; }
/* segment chips */
.contacts-segments { display: flex; gap: 6px; padding: 0 12px 8px; overflow-x: auto; flex: none; scrollbar-width: none; }
.contacts-segments::-webkit-scrollbar { display: none; }
.cseg { flex: none; font-size: 12px; padding: 5px 11px; border-radius: 999px; border: 1px solid var(--line); background: var(--ground); color: var(--ink-soft); cursor: pointer; white-space: nowrap; }
.cseg.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); font-weight: 600; }
.cseg-n { font-weight: 700; opacity: .8; margin-inline-start: 2px; }
/* filter row */
.contacts-filters { padding: 0 12px 8px; flex: none; display: flex; flex-direction: column; gap: 7px; }
.cfilter-row { display: flex; gap: 7px; }
.cfilter-sel, .cfilter-inp { flex: 1; min-width: 0; font-size: 12.5px; padding: 6px 9px; border: 1px solid var(--line); border-radius: 7px; background: var(--ground); color: var(--ink); }
.cfilter-sel:focus, .cfilter-inp:focus { outline: none; border-color: var(--accent); }
/* single horizontal row that scrolls sideways — stays one line no matter how many
   labels, so the filter area never grows tall. */
.cfilter-labels { display: flex; flex-wrap: nowrap; gap: 7px; overflow-x: auto; padding-bottom: 2px; scrollbar-width: thin; }
.cfilter-labels::-webkit-scrollbar { height: 6px; }
.cfilter-labels::-webkit-scrollbar-thumb { background: #c4d2cc; border-radius: 999px; }
.cflabel { flex: none; font-size: 11.5px; padding: 4px 11px; border-radius: 999px; border: 1px solid var(--line); background: var(--ground); color: var(--ink-soft); cursor: pointer; white-space: nowrap; }
.cflabel.on { background: var(--lc); border-color: var(--lc); color: var(--tc, #fff); font-weight: 600; }
/* list */
.contacts-items { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.contacts-items::-webkit-scrollbar { width: 10px; }
.contacts-items::-webkit-scrollbar-thumb { background: #c4d2cc; border: 3px solid transparent; background-clip: padding-box; border-radius: 999px; }
.contacts-empty, .contacts-muted { padding: 18px 16px; color: var(--ink-soft); font-size: 13px; }
.contacts-more { padding: 14px 16px; text-align: center; color: var(--ink-soft); font-size: 12px; }
.ctag { font-size: 9.5px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; vertical-align: middle;
  background: var(--ground); border: 1px solid var(--line); color: var(--ink-soft); border-radius: 4px; padding: 1px 5px; margin-inline-start: 4px; }
.contacts-muted { padding: 6px 0; }
.contact-row { display: flex; gap: 11px; align-items: flex-start; padding: 11px 14px; border-bottom: 1px solid var(--line); cursor: pointer; }
.contact-row:hover { background: var(--ground); }
.contact-row.active { background: var(--accent-soft); }
.contact-avatar { flex: none; width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; color: #fff; font-weight: 700; font-size: 15px; }
.contact-avatar.lg { width: 52px; height: 52px; font-size: 20px; }
.contact-row-body { flex: 1; min-width: 0; }
.contact-row-top { display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.contact-name { font-size: 14px; font-weight: 700; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.contact-when { font-size: 11.5px; color: var(--ink-soft); flex: none; }
.contact-handle { font-size: 12.5px; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin: 1px 0 4px; }
.contact-chips { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.csrc { font-size: 11px; padding: 1px 8px; border-radius: 999px; border: 1px solid var(--line); max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.csrc-wa { background: #eafaf1; border-color: #bfe6d0; color: #0c7a48; }
.csrc-mail { background: #fdeeee; border-color: #f5c9c9; color: #b3261e; }
.clchip { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.contact-count { flex: none; align-self: center; font-size: 12px; font-weight: 700; color: var(--ink-soft); background: var(--ground); border-radius: 999px; padding: 2px 9px; min-width: 26px; text-align: center; }
/* detail */
.contact-detail-head { display: flex; align-items: center; gap: 12px; padding: 16px 18px; border-bottom: 1px solid var(--line); background: var(--panel); flex: none; }
.contact-back { display: none; }
.contact-detail-name { font-size: 17px; font-weight: 700; color: var(--ink); }
.contact-detail-handle { font-size: 13px; color: var(--ink-soft); margin-top: 2px; }
.contact-detail-body { flex: 1; min-height: 0; overflow-y: auto; padding: 16px 18px 28px; }
.cstats { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; }
.cstat { flex: 1; min-width: 90px; background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; display: flex; flex-direction: column; gap: 2px; }
.cstat-v { font-size: 16px; font-weight: 700; color: var(--ink); }
.cstat-l { font-size: 11px; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .03em; }
.csection { margin-bottom: 20px; }
.csection h4 { margin: 0 0 8px; font-size: 13px; color: var(--ink); }
.csection-hint { font-weight: 400; color: var(--ink-soft); font-size: 11.5px; }
.cident { display: flex; justify-content: space-between; gap: 10px; align-items: center; padding: 8px 11px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); margin-bottom: 6px; }
.cident-name { font-weight: 600; font-size: 13.5px; color: var(--ink); }
.cident-src { font-size: 12px; color: var(--ink-soft); }
.cinherited { display: flex; flex-wrap: wrap; gap: 8px; }
.cichip { font-size: 11.5px; font-weight: 600; padding: 4px 11px; border-radius: 999px; border: 1px solid var(--lc); background: var(--lc); color: var(--tc, #fff); }
/* Properties in the contact detail reuse the chat's .cp-* widget styles verbatim. */
.crecents { display: flex; flex-direction: column; gap: 6px; }
.crecent { display: flex; align-items: center; gap: 9px; width: 100%; text-align: start; padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); cursor: pointer; }
.crecent:hover { border-color: var(--accent); }
.crecent-ic { flex: none; }
.crecent-t { flex: 1; min-width: 0; font-size: 13px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crecent-w { flex: none; font-size: 11.5px; color: var(--ink-soft); }

/* mobile: single pane, like Mail */
@media (max-width: 640px) {
  body[data-view="contacts"] #view { display: flex; overflow: hidden; }
  .contacts-list-pane, .contacts-detail-pane { flex: 1 1 auto; min-width: 0; min-height: 0; }
  .contacts-list-pane { border-inline-end: none; }
  body.contact-open .contacts-list-pane { display: none; }
  body:not(.contact-open) .contacts-detail-pane { display: none; }
  .contact-back { display: inline-flex; }
  .cfilter-sel, .cfilter-inp, .cp-input { font-size: 16px; }   /* no iOS zoom-on-focus */
  /* reading a contact takes the whole screen — reclaim the app top bar */
  body[data-view="contacts"].contact-open #topbar { display: none; }
  .contacts-head { padding: 12px 14px 6px; }
  .contacts-segments, .contacts-filters, .contacts-search { padding-inline-start: 12px; padding-inline-end: 12px; }
  .cstats { gap: 8px; }
  .cstat { min-width: 72px; padding: 9px 10px; }
}

/* Settings → Browser extension */
.ext-gen { margin: 8px 0 4px; }
.ext-token-card { margin-top: 12px; border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; background: var(--panel); max-width: 520px; }
.ext-token-card p { margin: 0 0 8px; font-size: 13px; }
.ext-token { display: flex; gap: 8px; align-items: center; }
.ext-token code { flex: 1; font-family: ui-monospace, Menlo, monospace; font-size: 12.5px; background: var(--ground); border: 1px solid var(--line); border-radius: 6px; padding: 7px 9px; overflow-x: auto; white-space: nowrap; }
.ext-sub { font-size: 12px; color: var(--ink-soft); }
.ext-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; border: 1px solid var(--line); border-radius: 10px; padding: 11px 14px; margin-bottom: 8px; background: var(--panel); }
.ext-row-info { display: flex; flex-direction: column; gap: 2px; }
.ext-empty { padding: 14px 0; color: var(--ink-soft); font-size: 13px; }
/* ================= AI connectors (settings) ================= */
/* Borrows the extension tab's row, token-card and sub styles wholesale — it is
   the same job (paste this somewhere, manage what is paired) and two visual
   languages for one job is how a settings screen stops looking like one
   product. What is new here is the address block and the two side-by-side
   guides. */
.mcp-url-card { border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; background: var(--panel); max-width: 620px; }
.mcp-url-label { font-size: 12px; color: var(--ink-soft); margin-bottom: 6px; }
.mcp-url { display: flex; gap: 8px; align-items: center; }
.mcp-url code { flex: 1; font-family: ui-monospace, Menlo, monospace; font-size: 13px; background: var(--ground); border: 1px solid var(--line); border-radius: 6px; padding: 8px 10px; overflow-x: auto; white-space: nowrap; }
.mcp-note { margin: 8px 0 0; font-size: 12px; color: var(--ink-soft); }
/* Two guides, side by side while there is room. They are read one at a time —
   nobody owns both apps — so they collapse to one column early rather than
   squeezing two columns of numbered steps onto a phone. */
.mcp-guides { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 12px; margin-top: 14px; max-width: 900px; }
.mcp-guide { border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; background: var(--panel); }
.mcp-guide h3 { margin: 0 0 8px; font-size: 14px; }
.mcp-guide ol { margin: 0; padding-inline-start: 18px; }
.mcp-guide li { font-size: 13px; line-height: 1.6; }
.mcp-facts { margin: 6px 0 0; padding-inline-start: 18px; max-width: 720px; }
.mcp-facts li { font-size: 13px; line-height: 1.65; margin-bottom: 5px; }
/* Says what a connection may do at a glance, because "can it message our
   customers?" is the only question worth asking of this list. */
/* align-self, because its parent (.ext-row-info) is a COLUMN flex container:
   without it the pill stretches to the full width of the card and stops reading
   as a badge. inline-block alone does nothing to a flex item. */
.mcp-tag { display: inline-block; align-self: flex-start; font-size: 11px; padding: 1px 7px; border-radius: 999px; border: 1px solid var(--line); color: var(--ink-soft); }
.mcp-tag.send { border-color: #cfa94d; color: #8a6a12; background: #fdf6e3; }
/* ================= Number-health banner ================= */
/* Sits directly under the topbar, above every view. Hidden until a real phone
   drops OR goes hours without receiving anything — a silent inbox is
   indistinguishable from a quiet one, and only this tells them apart. Clay, not
   the accent: this is a fault, not a promotion. */
#phone-banner { display: none; }
#phone-banner.show { display: block; }
.phone-banner-inner {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 16px;
  background: #FDF1EE;
  border-bottom: 1px solid #F0CFC6;
  color: #7A3D33;
  font-size: 14px; line-height: 1.5;
}
.pb-dot {
  width: 8px; height: 8px; border-radius: 50%; background: #8C4A3F; flex: none;
  animation: pb-pulse 2s ease-in-out infinite;
}
@keyframes pb-pulse { 0%,100% { opacity: 1 } 50% { opacity: .35 } }
@media (prefers-reduced-motion: reduce) { .pb-dot { animation: none } }
.pb-text { flex: 1 1 320px; min-width: 0; }
.pb-text b { font-weight: 680; }
.pb-cta {
  flex: none; padding: 6px 14px; border-radius: 8px; cursor: pointer;
  background: #8C4A3F; color: #fff; border: 1px solid #8C4A3F;
  font: inherit; font-weight: 640; font-size: 13.5px;
}
.pb-cta:hover { background: #773F35; }
.pb-cta:focus-visible { outline: 2px solid #8C4A3F; outline-offset: 2px; }
.pb-hint { flex: none; font-size: 13px; opacity: .8; }
/* The age beside each named number — the part that makes the banner actionable
   rather than merely alarming. */
.pb-age { white-space: nowrap; font-variant-numeric: tabular-nums; opacity: .85; }
.pb-cta:disabled { opacity: .6; cursor: progress; }
@media (max-width: 640px) { .phone-banner-inner { padding: 9px 12px; font-size: 13.5px; } }

/* ================= Personal chat privacy =================
   The post-scan tagging screen. It is the one modal in the app that appears
   before the product does anything, so it has to read as a decision rather than
   a settings panel: the lede states plainly that nothing has been stored yet,
   and each row's three options are named, not iconography. */
.tag-wrap { display: flex; flex-direction: column; min-height: 0; max-height: 74vh; }
.tag-lede {
  margin: 0 0 14px; font-size: 13.5px; line-height: 1.55; color: var(--ink-soft);
  border-inline-start: 3px solid var(--accent); padding-inline-start: 11px;
}
.tag-lede b { color: var(--ink); font-weight: 620; }
.tag-tools { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.tag-tools input[type="search"] {
  flex: 1 1 auto; min-width: 0; padding: 8px 11px; border: 1px solid var(--line);
  border-radius: 8px; background: var(--panel); color: var(--ink); font: inherit; font-size: 14px;
}
.tag-count { flex: none; font-size: 12.5px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* The scroll lives HERE, not on the modal: the lede and the Done button must
   stay put while a 900-chat list moves behind them. */
.tag-list {
  flex: 1 1 auto; min-height: 120px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
}
.tag-row {
  display: flex; align-items: center; gap: 12px; padding: 9px 12px;
  border-bottom: 1px solid var(--line);
}
.tag-row:last-child { border-bottom: none; }
/* The name truncates, the badge never does — hence two elements rather than a
   badge nested inside the truncating span, where the ellipsis would eat it. */
.tag-name { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 7px; font-size: 14px; }
/* The name gets a floor. Badges are flex:none, so without a basis here they
   take their width first and leave the name a few characters — and the name is
   what the whole screen is asking about. */
.tag-text { flex: 1 1 auto; min-width: 8ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tag-kind {
  flex: none; font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-soft); border: 1px solid var(--line); border-radius: 4px;
  padding: 1px 5px;
}
/* "Looks personal" — the classifier's guess, wearing its reason as a tooltip.
   Amber rather than red: this is a suggestion the user is about to confirm or
   overrule, not a warning and not a state. */
.tag-chip {
  flex: none; font-size: 11px; font-weight: 560; white-space: nowrap;
  color: var(--warn); background: #fdf6e7; border: 1px solid var(--warn);
  border-radius: 999px; padding: 1px 8px; cursor: help;
}
.tag-row.is-suggested { background: #fffdf7; }
/* Sits at the bottom of the list so "there are more" is a thing you can see and
   act on, rather than a silent truncation at the server's page size. */
.tag-more {
  display: block; width: 100%; padding: 11px; border: none; border-top: 1px solid var(--line);
  background: var(--ground); color: var(--accent); font: inherit; font-weight: 600;
  font-size: 13.5px; cursor: pointer;
}
.tag-more:hover { background: var(--accent-soft); }
.tag-more:disabled { color: var(--ink-soft); cursor: progress; }

/* "Your chats are waiting" — shown while a number is still holding its history.
   flex: none because #app is a column: without it this can be squeezed by
   whatever else competes for height, and its own text wraps to hide the damage. */
#privacy-banner { display: none; }
#privacy-banner.show { display: block; flex: 0 0 auto; width: 100%; }
.privacy-banner-inner {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 16px; font-size: 14px; line-height: 1.5;
  background: #fdf6e7; border-bottom: 1px solid #e8d5a8; color: #7a5713;
}
.pvb-ico { flex: none; font-size: 15px; }
.pvb-text { flex: 1 1 320px; min-width: 0; }
.pvb-text b { font-weight: 660; }
.pvb-cta {
  flex: none; padding: 6px 14px; border-radius: 8px; cursor: pointer;
  background: var(--warn); color: #fff; border: 1px solid var(--warn);
  font: inherit; font-weight: 640; font-size: 13.5px;
}
.pvb-cta:hover { filter: brightness(.94); }
.pvb-cta:focus-visible { outline: 2px solid var(--warn); outline-offset: 2px; }
@media (max-width: 640px) {
  .privacy-banner-inner { padding: 9px 12px; font-size: 13.5px; }
  .pvb-cta { width: 100%; padding: 10px 14px; }
}
.tag-seg { flex: none; display: flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.tag-opt {
  border: none; background: var(--panel); color: var(--ink-soft);
  font: inherit; font-size: 12.5px; font-weight: 560; padding: 6px 11px;
  cursor: pointer; border-inline-end: 1px solid var(--line); white-space: nowrap;
}
.tag-opt:last-child { border-inline-end: none; }
.tag-opt:hover { background: var(--ground); color: var(--ink); }
.tag-opt.on { color: #fff; }
.tag-opt.on.opt-none { background: var(--ink-soft); }
.tag-opt.on.opt-private { background: var(--warn); }
.tag-opt.on.opt-excluded { background: var(--danger); }
.tag-opt:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.tag-empty { padding: 26px 16px; text-align: center; color: var(--ink-soft); font-size: 13.5px; }
.tag-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.tag-note { font-size: 12.5px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* The per-chat chooser, used after the number is live. Radios rather than the
   segmented control: this one destroys data, so it gets a Save button and a
   sentence about what happens, not a single click. */
.tag-choices { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.tag-choice {
  display: flex; align-items: flex-start; gap: 10px; padding: 11px 13px;
  border: 1px solid var(--line); border-radius: 10px; cursor: pointer; background: var(--panel);
}
.tag-choice:hover { background: var(--ground); }
.tag-choice.on { border-color: var(--accent); background: var(--accent-soft); }
.tag-choice input { margin-top: 3px; flex: none; }
.tag-choice-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tag-choice-main b { font-size: 14px; font-weight: 620; }
.tag-choice-main small { font-size: 12.5px; color: var(--ink-soft); }
.tag-warn {
  border-inline-start: 3px solid var(--danger); background: #fdf0ef; color: #7c2620;
  padding: 10px 12px; border-start-end-radius: 8px; border-end-end-radius: 8px; font-size: 13px; line-height: 1.5;
  margin-bottom: 12px;
}
.tag-warn b { font-weight: 660; }

/* Phones: a number still holding its history says so, rather than looking
   connected-but-empty. */
.tag-held {
  display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px;
  color: var(--warn); border: 1px solid var(--warn); border-radius: 999px; padding: 1px 8px;
}

@media (max-width: 640px) {
  .tag-wrap { max-height: 82vh; }
  .tag-row { flex-wrap: wrap; gap: 8px; }
  .tag-seg { width: 100%; }
  /* 44px minimum on everything a thumb has to hit. These measured 35–41px,
     which works but sits under the touch-target guideline — and this screen
     asks somebody to make one decision per row, so a mis-tap is a chat tagged
     wrong rather than a menu that has to be reopened. Scoped to this screen's
     own selectors, so no shared button style changes. */
  .tag-opt { flex: 1 1 0; text-align: center; padding: 12px 6px; font-size: 13px; min-height: 44px; }
  .tag-more { min-height: 44px; }
  .tag-actions .btn { min-height: 44px; }
  /* 16px stops iOS Safari zooming the page when the search field is focused. */
  .tag-tools input[type="search"] { font-size: 16px; min-height: 44px; }
}
/* A tablet is a touch device at 768px, which is above the mobile breakpoint, so
   width alone left it with 28px targets. pointer: coarse asks the question that
   actually matters — is this being operated by a finger — and catches tablets,
   touchscreen laptops and phones in landscape alike. Same selectors only. */
@media (pointer: coarse) {
  .tag-opt, .tag-more, .tag-actions .btn, .tag-tools input[type="search"] { min-height: 44px; }
  .tag-tools input[type="search"] { font-size: 16px; }
}

/* ================= Billing (Settings → Billing) =================
   Logical properties throughout, so the eventual RTL pass is a stylesheet job. */
/* The "plan hero": one card that says what plan you have (big), one coloured
   pill for its state, one plain sentence for what happens next, what the plan
   includes, then the single action that fits the state. */
.bill-hero { max-inline-size: 520px; }
.bill-hero-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.bill-plan { font-weight: 700; font-size: 20px; line-height: 1.2; }
.bill-say { margin-block-start: 8px; font-size: 13.5px; color: var(--ink); line-height: 1.55; max-inline-size: 46ch; }
.bill-sep { block-size: 1px; background: var(--line); margin-block: 14px; }
.bill-contents { font-size: 13px; color: var(--ink-soft); }
.bill-selfhost { font-size: 12.5px; color: var(--ink-soft); }

/* The pill carries the state's colour. Each state is different NEWS — an active
   renewal and a bounced card must not look alike. */
.bill-pill {
  flex: none; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  padding-block: 3px; padding-inline: 9px; border-radius: 999px; white-space: nowrap;
}
.bill-pill-active     { background: #dcfce7; color: #15803d; }
.bill-pill-cancelling { background: #ffedd5; color: #c2410c; }
.bill-pill-trial      { background: #e0e7ff; color: #4338ca; }
.bill-pill-failing    { background: #fee2e2; color: #b91c1c; }
.bill-pill-free       { background: var(--ground); color: var(--ink-soft); }

.bill-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-block-start: 18px; }
.bill-hint { font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; max-inline-size: 60ch; margin-block-start: 12px; }

/* Extra mailboxes — a small purchase made from inside the billing screen. */
.bill-addon { margin-block-start: 14px; }
.bill-addon-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.bill-addon-price { font-size: 13px; color: var(--ink-soft); }
.bill-addon-say { font-size: 13.5px; color: var(--ink); line-height: 1.6; margin: 8px 0 0; max-inline-size: 60ch; }
.bill-addon-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-block-start: 14px; }
.bill-addon-count {
  min-inline-size: 44px; text-align: center; font-size: 17px; font-weight: 700;
  padding: 5px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--ground);
}
.bill-addon-total { font-size: 13px; color: var(--ink-soft); }
.bill-addon-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-block-start: 14px; }
.bill-addon-msg { font-size: 13px; color: var(--ink-soft); }
.bill-addon-msg.bad { color: var(--danger, #c4553c); }
.bill-addon-fine { font-size: 12px; color: var(--ink-soft); line-height: 1.5; margin: 12px 0 0; max-inline-size: 60ch; }
@media (max-width: 640px) {
  /* Real tap targets on the two controls someone uses to spend money. */
  .bill-addon-row .btn { min-inline-size: 44px; min-block-size: 44px; }
  .bill-addon-actions .btn { flex: 1 1 100%; min-block-size: 44px; }
  .bill-addon-total { flex-basis: 100%; }
}
.bill-confirming { display: flex; align-items: center; gap: 10px; font-size: 13.5px; margin-block-end: 14px; }
@media (max-width: 640px) {
  .bill-actions .btn { flex: 1 1 auto; min-block-size: 40px; }
}

/* ================= CRM module (deals, follow-ups, contact memory) =================
   Every rule here uses LOGICAL properties — margin-inline-start, not
   margin-left; padding-block, not padding-top; border-inline-start, not
   border-left. The app has no RTL pass yet and this module does not fix that,
   but it must not add to the pile: logical properties cost nothing today and
   are the difference between the eventual RTL project taking two weeks or six.
*/

/* --- shared bits: sections, fields, chips --- */
.crm-section { border-block-start: 1px solid var(--line); }
.crm-section h4 { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.crm-hint { font-size: 12px; color: var(--ink-soft); line-height: 1.45; margin-block-start: 6px; }
.crm-field { display: flex; align-items: center; gap: 8px; margin-block-start: 8px; }
.crm-field-wide { max-inline-size: 420px; }
.crm-label { flex: 0 0 96px; font-size: 12px; color: var(--ink-soft); }
.crm-input {
  flex: 1 1 auto; min-inline-size: 0; font: inherit; font-size: 13px;
  padding-block: 6px; padding-inline: 8px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--panel); color: var(--ink);
}
.crm-input:disabled { opacity: .65; cursor: not-allowed; }
textarea.crm-input { resize: vertical; }
.crm-money { display: flex; align-items: center; flex: 1 1 auto; min-inline-size: 0; gap: 6px; }
.crm-cur { font-size: 11.5px; font-weight: 700; color: var(--ink-soft); }
.crm-mini {
  font: inherit; font-size: 11.5px; font-weight: 600; cursor: pointer;
  padding-block: 3px; padding-inline: 8px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--ground); color: var(--ink-soft);
}
.crm-mini:hover { color: var(--ink); }
.crm-mini.danger { color: var(--danger); }
.crm-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-block-start: 8px; }
.crm-chip {
  font: inherit; font-size: 12px; cursor: pointer;
  padding-block: 4px; padding-inline: 10px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--ground); color: var(--ink-soft);
}
.crm-chip:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.crm-chip-date {
  font: inherit; font-size: 12px; padding-block: 3px; padding-inline: 8px;
  border-radius: 999px; border: 1px solid var(--line); background: var(--ground); color: var(--ink-soft);
}

/* --- one colour language for the five stages -------------------------------
   Five identical grey chips tell you nothing: a seller glancing at the panel
   cannot see whether this enquiry is alive or dead. Each stage gets its own hue,
   set once here as a custom property and inherited by every surface that shows a
   stage — the chat panel pills, the board columns and cards, the contact deal
   rows and the dashboard strip. Colour is never the ONLY signal: Won and Lost
   also carry a glyph, so the state survives a colour-blind reader and a
   greyscale screenshot.

   The progression is deliberate — cool grey while nothing has been promised,
   amber once a number is on the table, indigo while it is being argued over,
   then green or red. */
[data-stage="new"]         { --st: #64748b; --st-soft: #f1f5f9; }
[data-stage="quoted"]      { --st: #b45309; --st-soft: #fef3c7; }
[data-stage="negotiating"] { --st: #4338ca; --st-soft: #e0e7ff; }
[data-stage="won"]         { --st: #15803d; --st-soft: #dcfce7; }
[data-stage="lost"]        { --st: #b91c1c; --st-soft: #fee2e2; }

/* --- the deal section in the chat details panel --- */
.crm-track { inline-size: 100%; }
.crm-deal-title { font-weight: 650; font-size: 13.5px; margin-block-start: 6px; overflow-wrap: anywhere; }
.crm-stages { display: flex; flex-wrap: wrap; gap: 4px; margin-block-start: 8px; }
.crm-stage {
  /* ~30% basis so the row breaks 3 + 2 in a narrow panel: the three stages where
     a deal is still alive, then the two that end it. A 4 + 1 wrap leaves "Lost"
     stretched across its own row looking like a mistake. */
  flex: 1 1 28%; font: inherit; font-size: 11.5px; font-weight: 600; cursor: pointer;
  padding-block: 5px; padding-inline: 6px; border-radius: 8px;
  /* Tinted even when inactive, so the row reads as five distinct steps rather
     than one undifferentiated strip. */
  background: var(--st-soft); color: var(--st);
  border: 1px solid color-mix(in srgb, var(--st) 25%, transparent);
}
.crm-stage:hover:not(:disabled) { border-color: var(--st); }
.crm-stage:disabled { cursor: default; opacity: .75; }
/* The current stage is filled, not merely outlined — the one you can find
   without reading. */
.crm-stage.on {
  background: var(--st); color: #fff; border-color: var(--st); font-weight: 700;
  box-shadow: 0 1px 3px color-mix(in srgb, var(--st) 35%, transparent);
}
.crm-stage-glyph { font-weight: 800; margin-inline-end: 3px; }
/* Name the control and show its current value, so a row of five words reads as
   a status picker rather than as decoration. */
.crm-stage-head { display: flex; align-items: center; gap: 8px; margin-block-start: 10px; }
.crm-stage-head .crm-label { flex: none; }
.crm-stage-now {
  font-size: 11px; font-weight: 700; padding-block: 2px; padding-inline: 8px;
  border-radius: 999px; background: var(--st-soft); color: var(--st);
  border: 1px solid color-mix(in srgb, var(--st) 25%, transparent);
}
/* The deal slide-over reuses the ticket panel's chrome; only its own bits here. */
.dp-links { display: flex; flex-wrap: wrap; gap: 6px; }
.ticket-panel .crm-stages { margin-block-start: 6px; }
.ticket-panel .crm-fu { border-block-end: none; padding-block: 4px; }
.crm-deal-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-block-start: 10px; }

/* --- follow-up rows --- */
.crm-fu {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 8px;
  padding-block: 7px; border-block-end: 1px dashed var(--line);
}
.crm-fu:last-of-type { border-block-end: none; }
.crm-fu-main { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }
.crm-fu-when { font-size: 12px; font-weight: 650; }
.crm-fu.due-over .crm-fu-when { color: var(--danger); }
.crm-fu-note { font-size: 12.5px; overflow-wrap: anywhere; }
.crm-fu-who { font-size: 11.5px; color: var(--ink-soft); }
.crm-fu-acts { display: flex; gap: 4px; flex: none; }

/* --- Contacts | Pipeline tabs --- */
.crm-tabs { display: flex; gap: 6px; padding-inline: 12px; padding-block-end: 8px; flex: none; }
.crm-tab {
  font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
  padding-block: 5px; padding-inline: 12px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--ground); color: var(--ink-soft);
}
.crm-tab.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.cseg-crm { font-weight: 650; }

/* --- contact detail CRM sections --- */
.crm-owner-row { display: flex; flex-wrap: wrap; gap: 12px; }
.crm-owner-row .crm-field { flex: 1 1 220px; margin-block-start: 0; }
.crm-drow {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
  padding-block: 7px; border-block-end: 1px dashed var(--line);
}
.crm-drow.closed { opacity: .6; }
.crm-drow-title { flex: 1 1 auto; min-inline-size: 0; font-size: 13px; overflow-wrap: anywhere; }
.crm-drow-val { font-size: 12.5px; font-variant-numeric: tabular-nums; }
.crm-dpill {
  flex: none; font-size: 11px; font-weight: 650; padding-block: 2px; padding-inline: 8px;
  border-radius: 999px; background: var(--st-soft); color: var(--st);
  border: 1px solid color-mix(in srgb, var(--st) 25%, transparent);
}
.crm-note { padding-block: 7px; border-block-end: 1px dashed var(--line); }
.crm-note-body { font-size: 13px; white-space: pre-wrap; overflow-wrap: anywhere; }
.crm-note-meta { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--ink-soft); margin-block-start: 3px; }
.crm-act { display: flex; align-items: baseline; gap: 8px; padding-block: 5px; font-size: 12.5px; }
.crm-act-when { flex: 0 0 80px; color: var(--ink-soft); font-size: 11.5px; }
.crm-act-what { flex: 1 1 auto; min-inline-size: 0; overflow-wrap: anywhere; }
.crm-act-who { flex: none; color: var(--ink-soft); font-size: 11.5px; }

/* --- the pipeline board --- */
.pipeline-pane { flex: 1 1 auto; display: flex; flex-direction: column; min-inline-size: 0; min-block-size: 0; background: var(--panel); }
.pipeline-board { flex: 1 1 auto; min-block-size: 0; overflow: auto; padding-inline: 12px; padding-block-end: 16px; }
.pipe-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-block-end: 8px; }
.pipe-cols { display: flex; gap: 10px; align-items: flex-start; }
.pipe-col {
  flex: 1 1 0; min-inline-size: 180px; background: var(--ground);
  border: 1px solid var(--line); border-radius: 12px; padding: 8px;
  /* The column wears its stage colour as a cap, so the board reads left-to-right
     as a progression instead of five grey boxes. */
  border-block-start: 3px solid var(--st);
}
.pipe-col-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.pipe-col-name { font-size: 12.5px; font-weight: 700; color: var(--st); }
.pipe-col-n {
  font-size: 11px; font-weight: 700; color: var(--st); background: var(--st-soft);
  padding-block: 1px; padding-inline: 7px; border-radius: 999px;
}
.pipe-col-sum { display: flex; flex-direction: column; font-size: 12px; font-variant-numeric: tabular-nums; color: var(--ink-soft); margin-block: 2px 8px; }
.pipe-col-body { display: flex; flex-direction: column; gap: 8px; }
.pipe-card {
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  padding: 9px; cursor: pointer;
  border-inline-start: 3px solid var(--st);
}
.pipe-card:hover { border-color: var(--accent); border-inline-start-color: var(--st); }
.pipe-card-top { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.pipe-contact { font-size: 12px; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pipe-title { font-size: 12.5px; margin-block: 3px; overflow-wrap: anywhere; }
.pipe-card-foot { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ink-soft); }
.pipe-value { font-weight: 650; color: var(--ink); font-variant-numeric: tabular-nums; }
.pipe-days { flex: 1 1 auto; }
.pipe-owner {
  flex: none; inline-size: 20px; block-size: 20px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent); font-size: 10.5px; font-weight: 700;
}
.pipe-advance {
  margin-block-start: 8px; inline-size: 100%; font: inherit; font-size: 11.5px; font-weight: 650;
  cursor: pointer; padding-block: 4px; border-radius: 8px;
  background: var(--st-soft); color: var(--st);
  border: 1px solid color-mix(in srgb, var(--st) 25%, transparent);
}
.pipe-advance:hover { background: var(--st); color: #fff; border-color: var(--st); }
.pipe-empty { display: flex; flex-direction: column; align-items: center; gap: 10px; padding-block: 48px; color: var(--ink-soft); font-size: 13px; }

/* --- dashboard strip --- */
.crm-strip-row { margin-block: 4px 10px; }
.crm-strip-vals { display: flex; flex-direction: column; }
.crm-strip-vals .s-val { font-size: 18px; }
.crm-stages-mini { display: flex; flex-wrap: wrap; gap: 8px; }
.crm-stage-mini {
  flex: 1 1 120px; display: flex; flex-direction: column; gap: 1px;
  border-inline-start: 3px solid var(--st); padding-inline-start: 8px;
}
.crm-stage-mini-n { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--st); }
.crm-stage-mini-l { font-size: 11.5px; color: var(--ink-soft); }
.crm-stage-mini-v { font-size: 11.5px; font-variant-numeric: tabular-nums; }
.crm-overdue { margin-block-start: 10px; border-block-start: 1px solid var(--line); padding-block-start: 8px; }
.crm-overdue-row { display: flex; align-items: center; justify-content: space-between; font-size: 12.5px; padding-block: 3px; }
.crm-overdue-n { font-weight: 700; color: var(--danger); font-variant-numeric: tabular-nums; }

/* --- the undo toast --- */
.toast-action { display: flex; align-items: center; gap: 12px; }
.toast-btn {
  font: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer;
  background: transparent; border: none; color: #7ee2b8; text-decoration: underline;
  padding: 0;
}

/* ================= CRM v1.1 ================= */

/* --- recurring follow-ups --- */
.crm-repeat .crm-input { max-inline-size: 180px; }
.crm-repeat-badge { color: var(--accent); font-weight: 700; }

/* --- drag-and-drop on the board (an addition to click-to-advance) --- */
.pipe-card[draggable="true"] { cursor: grab; }
.pipe-card.dragging { opacity: .45; cursor: grabbing; }
.pipe-col.drop-target {
  background: var(--st-soft);
  outline: 2px dashed var(--st); outline-offset: -4px;
}
.pipe-col-body { min-block-size: 40px; }

/* --- bulk actions over contacts --- */
.contacts-bulkbar { padding-inline: 12px; padding-block-end: 6px; flex: none; }
.cbulk { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.cbulk-n { font-size: 12px; color: var(--ink-soft); margin-inline-end: 2px; }
.contact-row.bulk { padding-inline-start: 8px; }
.contact-check { flex: none; inline-size: 16px; block-size: 16px; margin-inline-end: 2px; accent-color: var(--accent); }
.crm-check { display: flex; align-items: center; gap: 8px; font-size: 12.5px; margin-block-start: 10px; color: var(--ink-soft); }

/* --- CSV import --- */
.imp-preview { margin-block-start: 12px; }
.imp-stats { display: flex; flex-wrap: wrap; gap: 12px; font-size: 13px; font-weight: 650; }
.imp-problems {
  margin-block-start: 8px; max-block-size: 160px; overflow-y: auto;
  font-size: 12px; color: var(--danger); border-inline-start: 3px solid var(--danger);
  padding-inline-start: 8px;
}

/* --- deals on a mail thread ---
   It rides in the thread's header row now rather than in a band of its own: a
   full row of mostly-empty height to hold one button is height taken from the
   email underneath it. No background and no rule, because it is no longer a
   section — it is the right-hand end of a row that already exists. */
.mail-crm {
  display: flex; flex-wrap: nowrap; align-items: center; gap: 8px;
  flex: none; margin-inline-start: auto; min-width: 0;
}
.mail-crm .mail-crm-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 14ch; }
.mail-crm-title { font-size: 12.5px; font-weight: 650; }
.mail-crm-val { font-size: 12.5px; font-variant-numeric: tabular-nums; }

/* --- per-agent report --- */
.crm-agents table.agents { inline-size: 100%; }
.ag-overdue { color: var(--danger); font-weight: 700; }

/* --- stage renaming --- */
.ws-stages { margin-block-start: 10px; }
.ws-stage-dot {
  display: inline-block; inline-size: 8px; block-size: 8px; border-radius: 50%;
  background: var(--st); margin-inline-end: 6px;
}

@media (max-width: 900px) {
  /* Five columns do not fit a phone: scroll them horizontally rather than
     crushing every card to an unreadable width. 78vw leaves a sliver of the next
     column visible, which is what tells a thumb the strip scrolls. */
  .pipe-cols { overflow-x: auto; padding-block-end: 8px; scroll-snap-type: x proximity; }
  .pipe-col { flex: 0 0 78vw; max-inline-size: 300px; scroll-snap-align: start; }
  .crm-label { flex-basis: 84px; }
  /* Dragging a card inside a horizontally-scrolling strip on a touch screen is a
     fight nobody wins — the tap-to-advance button is the path here. */
  .pipe-card[draggable="true"] { cursor: pointer; }
}

@media (max-width: 640px) {
  /* Touch sizing. These controls are new, so they start at a size a thumb can
     actually hit rather than inheriting the desktop density — 36px for anything
     that changes state, a little less for secondary actions.
     16px on every text input is not a style choice: iOS Safari zooms the page
     when it focuses an input smaller than that, and the zoom does not undo. */
  .crm-stage { min-block-size: 36px; font-size: 12.5px; }
  .crm-tab { min-block-size: 36px; }
  .pipe-advance { min-block-size: 36px; font-size: 12.5px; }
  .crm-chip, .crm-chip-date { min-block-size: 34px; display: inline-flex; align-items: center; }
  .crm-mini { min-block-size: 32px; padding-inline: 12px; display: inline-flex; align-items: center; }
  .crm-input, .tf-search, .tf-select { min-block-size: 38px; font-size: 16px; }
  .crm-chip-date, .crm-check input, .tf-check input { font-size: 16px; }
  /* The box itself is 24px, but the target is the whole row: in bulk mode a tap
     anywhere on a contact toggles it (see bindContactRows), so the thumb has a
     ~64px-tall strip to hit rather than a checkbox. */
  .contact-check { inline-size: 24px; block-size: 24px; }
  .contact-row.bulk { padding-block: 12px; }
  .crm-fu-acts { gap: 6px; }
  /* The stage row is the CRM's primary control: give it two comfortable rows of
     larger pills rather than five cramped ones. */
  .crm-stages { gap: 6px; }
  .crm-stage { flex: 1 1 30%; }
  /* Field rows stack: a 96px label beside a 38px input leaves nothing for the
     input on a 390px screen. */
  .crm-field { flex-wrap: wrap; }
  .crm-field .crm-label { flex: 1 1 100%; margin-block-end: -2px; }
  .crm-owner-row .crm-field { flex: 1 1 100%; }
}

/* ---------- two-factor nag banner ----------
 *
 * Fixed rather than a flex child of #app: #app is height:100vh and every view
 * switches inside it, so a banner living in there is one refactor away from
 * being dropped on some screen. Out here it cannot be, and #app is shortened by
 * exactly the banner's height so nothing is covered.
 *
 * No dismiss control, by design. It goes away when 2FA is on — that is the
 * whole mechanism, now that the server no longer refuses un-enrolled sessions.
 */
:root { --tfb-h: 40px; }
body.has-2fa-banner #app { height: calc(100vh - var(--tfb-h)); margin-top: var(--tfb-h); }

#wa-2fa-banner {
  position: fixed; top: 0; inset-inline-start: 0; inset-inline-end: 0; z-index: 99997;
  min-height: var(--tfb-h); box-sizing: border-box;
  display: flex; align-items: center; gap: 10px;
  padding: 6px 14px;
  background: #b3261e; color: #fff;
  font-size: 13px; line-height: 1.35;
  box-shadow: 0 1px 6px rgba(0, 0, 0, .18);
}
#wa-2fa-banner .tfb-text { flex: 1 1 auto; min-width: 0; }
#wa-2fa-banner b { font-weight: 700; }

/* The dot carries the urgency so the type does not have to shout in caps. */
#wa-2fa-banner .tfb-dot {
  flex: none; width: 8px; height: 8px; border-radius: 50%;
  background: #ffd9d5; box-shadow: 0 0 0 0 rgba(255, 217, 213, .7);
  animation: tfb-pulse 2.4s ease-out infinite;
}
@keyframes tfb-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(255, 217, 213, .7); }
  70%  { box-shadow: 0 0 0 7px rgba(255, 217, 213, 0); }
  100% { box-shadow: 0 0 0 0 rgba(255, 217, 213, 0); }
}
@media (prefers-reduced-motion: reduce) {
  #wa-2fa-banner .tfb-dot { animation: none; }
}

#wa-2fa-banner .tfb-cta {
  flex: none; min-height: 28px; padding: 5px 12px;
  border: 1px solid rgba(255, 255, 255, .55); border-radius: 7px;
  background: rgba(255, 255, 255, .12); color: #fff;
  font: inherit; font-weight: 600; cursor: pointer;
}
#wa-2fa-banner .tfb-cta:hover { background: rgba(255, 255, 255, .22); }
#wa-2fa-banner .tfb-cta:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* Narrow screens: the sentence wraps to two lines, so the reserved height has to
 * grow with it or the banner sits on top of the org name in the top bar. */
@media (max-width: 620px) {
  :root { --tfb-h: 58px; }
  #wa-2fa-banner { flex-wrap: wrap; gap: 6px 10px; }
  #wa-2fa-banner .tfb-text { flex: 1 1 100%; order: 2; }
  #wa-2fa-banner .tfb-cta { order: 1; margin-inline-start: auto; }
  #wa-2fa-banner .tfb-dot { order: 0; }
}

/* An edit rewrites the message in place, so the meta line has to say so —
   otherwise text changes under the reader with no explanation. Quiet on
   purpose: it is a footnote about the message, not a state of it. */
.edited-tag {
  font-size: 11px;
  color: var(--ink-soft, #5c6b65);
  opacity: .75;
  font-style: italic;
}
.edited-tag::before { content: '· '; font-style: normal; }

/* Filter by what a chat is set to now. Without this the ones you already tagged
   are unreachable: the list orders by last activity, and excluding a chat clears
   it, so those rows sit at the bottom of hundreds. */
.tag-filters { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 10px; }
.tag-filter {
  font: inherit; font-size: 12px; font-weight: 600;
  padding: 5px 11px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line, #e3e8e5);
  background: var(--panel, #fff); color: var(--ink-soft, #5a6b65);
  font-variant-numeric: tabular-nums;
}
.tag-filter:hover { border-color: var(--accent, #1e9e6a); }
.tag-filter.on {
  background: var(--accent, #1e9e6a); border-color: var(--accent, #1e9e6a); color: #fff;
}
.tag-filter:focus-visible { outline: 2px solid var(--accent, #1e9e6a); outline-offset: 2px; }

/* The outbox gave up on this message. Red text rather than another icon: the
   reader must not have to decode what a fourth tick-glyph means. */
.msg-meta .send-failed { color: #b3261e; font-weight: 600; }

/* Privacy hold: informational amber, not incident red — a decision pending. */
.phone-banner-inner.pb-hold { background: #FBF3E2; border-bottom-color: #EAD9AE; color: #6B5212; }
.phone-banner-inner.pb-hold .pb-dot { background: #C99A2E; }

/* Working hours, on the Edit-teammate modal.
   A seven-row grid rather than seven form-rows: the day, the two times and the
   word between them line up in fixed columns, so an eye running down the list is
   comparing start against start. Times that wander left and right by the width of
   "Wednesday" are the reason a wrong day goes unnoticed. */
.wh-block { border-top: 1px solid var(--line); padding-top: 14px; margin-top: 16px; }
.wh-lede, .wh-hint { font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; font-weight: 400; }
.wh-lede { margin: 0 0 10px; }
.wh-hint { margin: 10px 0 0; }
.wh-days { display: flex; flex-direction: column; gap: 6px; }
.wh-row { display: grid; grid-template-columns: 118px 1fr auto 1fr auto; align-items: center; gap: 8px; }
.wh-day { margin: 0 !important; display: flex; align-items: center; gap: 7px; font-size: 13px;
  font-weight: 400 !important; color: var(--ink) !important; text-transform: none !important; }
.modal .wh-row input[type="time"] { width: 100%; padding: 6px 8px; border: 1px solid var(--line);
  border-radius: 6px; background: var(--panel); color: var(--ink); font-size: 13px; }
/* Disabled reads as "this day is off", so it has to be visibly inert rather than
   merely unclickable — an unticked day whose times still look editable invites
   somebody to type into them and wonder why nothing saved. */
.modal .wh-row input[type="time"]:disabled { opacity: 0.45; background: var(--ground); }
.wh-to { font-size: 12px; color: var(--ink-soft); }
.wh-more { font-size: 11px; color: var(--ink-soft); background: var(--ground);
  border: 1px solid var(--line); border-radius: 999px; padding: 1px 7px; white-space: nowrap; }

@media (max-width: 560px) {
  /* Two lines per day instead of a five-column grid squeezed into a phone: the
     day above, the times below it, which is still one glance per day. */
  .wh-row { grid-template-columns: 1fr auto 1fr auto; }
  .wh-day { grid-column: 1 / -1; }
}

/* ---- Mail: the list/reader splitter ----
   At rest it is exactly the 1px rule it replaces. The element around it is 9px
   wide so there is something to actually hit — a 1px target is a target you miss
   — and that width is taken out of the reader rather than added to the layout,
   so nothing shifts when the handle appears. */
.mail-splitter {
  flex: 0 0 9px; position: relative; align-self: stretch; cursor: col-resize;
  background: transparent; border: none; padding: 0; touch-action: none;
}
.mail-splitter::before {
  content: ''; position: absolute; top: 0; bottom: 0; inset-inline-start: 4px; width: 1px;
  background: var(--line); transition: background .12s ease, box-shadow .12s ease;
}
.mail-splitter:hover::before, .mail-splitter:focus-visible::before,
body.mail-resizing .mail-splitter::before { background: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.mail-splitter:focus-visible { outline: none; }

/* The handle: hidden until the line is hovered or focused, because a control
   parked in the middle of the page at all times is one more thing to read past
   on every visit. */
.mail-splitter-grip {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 18px; height: 44px; padding: 0; display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: 9px; background: var(--panel);
  color: var(--ink-soft); cursor: pointer; z-index: 3;
  opacity: 0; transition: opacity .12s ease, color .12s ease, border-color .12s ease;
}
.mail-splitter:hover .mail-splitter-grip,
.mail-splitter:focus-visible .mail-splitter-grip,
.mail-splitter-grip:focus-visible { opacity: 1; }
.mail-splitter-grip:hover { color: var(--accent); border-color: var(--accent); }
.mail-splitter-grip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.mail-splitter-chev { font-size: 15px; line-height: 1; transition: transform .15s ease; }
/* Points the way it will move the pane, so the same control reads as "wider"
   and then as "back" without changing shape. */
.mail-splitter-grip.is-wide .mail-splitter-chev { transform: rotate(180deg); }

/* Text selection while dragging turns the whole page blue on the first sweep. */
body.mail-resizing { cursor: col-resize; user-select: none; }
body.mail-resizing iframe { pointer-events: none; }

/* ---- Mail: the filter panel ---- */
.mail-search { display: flex; gap: 8px; align-items: center; }
.mail-search .search-input { flex: 1 1 auto; min-width: 0; }
.mail-filter-btn { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.mail-filter-count { display: none; min-width: 17px; height: 17px; padding: 0 5px; border-radius: 9px;
  background: var(--accent); color: #fff; font-size: 11px; line-height: 17px; text-align: center; }
.mail-filter-count.on { display: inline-block; }

/* ---- Mail: selecting threads ---- */
.mail-selbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 14px; border-bottom: 1px solid var(--line); background: var(--accent-soft); }
.msel-count { font-size: 12.5px; color: var(--ink-soft); }
/* Pushed right so the two destructive-ish actions sit together and away from
   Select all, which is the one people hit by accident. */
.msel-actions { margin-inline-start: auto; display: flex; gap: 6px; }
.mail-pick { display: grid; place-items: center; flex: 0 0 auto; padding-inline-end: 2px; }
.mail-pick input { width: 16px; height: 16px; cursor: pointer; }
/* The whole row is the hit target in select mode, so it has to look like one. */
.mail-list-pane.selecting .mail-row { cursor: pointer; }
.mail-list-pane.selecting .mail-row:hover { background: var(--ground); }
.mail-row.picked { background: var(--accent-soft); }
.mail-row.picked:hover { background: var(--accent-soft); }

.mail-empty .btn { margin-top: 10px; }

@media (max-width: 640px) {
  /* Half the screen is too much of a phone to give a filter panel. */
  .mail-filter-panel { max-height: 40vh; }
  .msel-actions { margin-inline-start: 0; width: 100%; }
}

/* ---- Mail: multi-select filter dropdowns ----
   The popover is ABSOLUTE, so opening it costs the layout nothing: the mail list
   stays exactly where it was instead of being pushed down by however many labels
   the workspace happens to have. That is the whole reason this is not an inline
   panel — with fifty teammates the inline version put the mail off screen. */
.mail-filterbar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  padding: 0 14px 10px; }
.mdd { position: relative; }
.mdd-trigger { display: inline-flex; align-items: center; gap: 5px; padding: 5px 10px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--panel);
  font-size: 12.5px; font-weight: 600; color: var(--ink-soft); cursor: pointer; }
.mdd-trigger:hover { border-color: var(--accent); color: var(--accent); }
.mdd-trigger.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.mdd-badge { min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px;
  background: var(--accent); color: #fff; font-size: 10.5px; line-height: 16px; text-align: center; }
.mdd-caret { font-size: 10px; opacity: .7; }
.mdd.open .mdd-trigger { border-color: var(--accent); color: var(--accent); }

.mdd-pop { position: absolute; top: calc(100% + 4px); inset-inline-start: 0; z-index: 40;
  width: 260px; max-width: calc(100vw - 28px);
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 12px 30px rgba(20, 30, 26, .18); padding: 8px; }
/* Typing is what makes fifty teammates workable — scrolling a list that long to
   find one person is the thing this replaces. */
.mdd-search { width: 100%; padding: 6px 9px; margin-bottom: 6px; font-size: 13px;
  border: 1px solid var(--line); border-radius: 7px; background: var(--ground); color: var(--ink); }
.mdd-search:focus { outline: none; border-color: var(--accent); }
.mdd-list { max-height: min(240px, 40vh); overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: 2px; }
.mdd-opt { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 7px;
  font-size: 13px; cursor: pointer; }
.mdd-opt:hover { background: var(--ground); }
.mdd-opt.on { color: var(--accent); font-weight: 600; }
.mdd-opt span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mdd-opt input { width: 15px; height: 15px; flex: 0 0 auto; cursor: pointer; }
.mdd-none { padding: 10px 8px; font-size: 12.5px; color: var(--ink-soft); }
.mdd-clear, .mdd-clear-all { border: none; background: none; cursor: pointer;
  font-size: 12px; font-weight: 600; color: var(--accent); padding: 6px 8px 2px; }
.mdd-clear:hover, .mdd-clear-all:hover { text-decoration: underline; }
.mdd-clear { display: block; width: 100%; text-align: start; border-top: 1px solid var(--line); margin-top: 6px; }

@media (max-width: 640px) {
  /* Anchored to the bar rather than the trigger, so a dropdown near the right
     edge does not open off screen. */
  .mdd { position: static; }
  .mdd-pop { inset-inline-start: 14px; inset-inline-end: 14px; width: auto; }
  .mail-filterbar { position: relative; }
}

/* ---- Mail on touch ----
 *
 * Split by INPUT rather than by width. A tablet is 900px wide and has no hover
 * at all, so a width breakpoint gets it exactly wrong: it keeps the desktop
 * layout, including a handle that only ever appears on :hover — a control that
 * is, on that device, invisible and therefore absent.
 */
@media (hover: none) {
  /* Nothing will ever hover this, so it is simply always there. */
  .mail-splitter-grip { opacity: 1; }
  .mail-splitter { flex-basis: 22px; }
  .mail-splitter::before { inset-inline-start: 10px; }
}
@media (pointer: coarse) {
  /* 44px, the smallest thing a finger hits reliably. At 28px these pills read as
     buttons and behave as near-misses. */
  .mdd-trigger { min-height: 44px; padding: 8px 14px; font-size: 13.5px; }
  .mdd-opt { min-height: 44px; padding: 10px 10px; font-size: 14px; }
  .mdd-opt input { width: 20px; height: 20px; }
  .mail-pick input { width: 22px; height: 22px; }
  .mail-selbar .btn.sm { min-height: 40px; }
  /* The two things a thumb presses on the AI connectors page. Copy is the whole
     point of that screen, and Revoke is the one people reach for in a hurry. */
  .mcp-url .btn, #mcp-tokens .btn { min-height: 44px; }
  /* Answering a suggested label is a one-tap decision taken while reading a
     conversation — the worst possible moment for a near-miss. The ✕ is the
     smallest and the most consequential, since it teaches the classifier no. */
  .lsug .btn.sm { min-height: 40px; padding-inline-start: 14px; padding-inline-end: 14px; }
  .lb-auto input[type=checkbox] { width: 20px; height: 20px; }
  /* Send test, Deliveries, Redeliver — all pressed in a hurry, by somebody
     already annoyed that an integration is not working. */
  .wh-acts .btn, .wh-log-row .btn, .wh-f-acts .btn { min-height: 44px; }
  .wh-ev-row { padding: 9px 6px; }
  .wh-ev-row input, .wh-lb input { width: 20px; height: 20px; }
  /* 16px or iOS Safari zooms the viewport the moment the URL field is focused. */
  .wh-f input { font-size: 16px; }
  /* 16px or iOS Safari zooms the whole viewport the moment this is focused, and
     the list you were filtering slides off to the right. The same reason
     .twofa-row input and the notification selects are pinned to 16px. */
  .mdd-search { font-size: 16px; min-height: 40px; }

  /* ---- reading and answering a thread with a thumb ----
     Everything below measured 26–34px, which is the size that reads as a button
     and behaves as a near-miss. The reader's own toolbar is already icon-only at
     this width (the @container rule above), so the room exists — these were
     small because they were drawn for a mouse, not because anything is tight. */
  /* min-, not a fixed width: a tablet is coarse AND wide enough to keep the
     labels, so a hard 44px would clip "Mark unread" down to "Mar". min-width
     also beats the container rule's `width: 34px` without having to repeat it. */
  .mail-tb { min-width: 44px; min-height: 44px; }
  .mail-msg-act button { width: 40px; height: 40px; font-size: 15px; }
  .mail-compose-btn { width: 40px; height: 40px; font-size: 15px; }
  .mail-reply-trigger { min-height: 44px; }
  .mail-msg-collapsed { min-height: 44px; }
  .mail-quote-toggle { min-height: 36px; padding: 6px 12px; }
  /* Menu rows are a list of choices read one at a time — the height that makes
     them hittable is the same height that makes them legible. */
  .mail-tb-opt { min-height: 44px; font-size: 14px; }

  /* NO ZOOM-ON-FOCUS IN THE COMPOSER.
     Tapping To or the body with these under 16px makes iOS Safari zoom the page,
     and a position:fixed window in a zoomed viewport is half off the screen with
     the Send button somewhere to the right. This is the whole window, so it is
     the worst place in the app for it to happen. */
  .mrc-input, .mail-compose-text, .mail-search .search-input { font-size: 16px; }
  .mrc-field { padding-block: 10px; }

  /* A dropdown must not run off the side of the pane it belongs to. */
  .mail-tb-menu, .mail-tpl-menu { max-width: min(300px, calc(100vw - 32px)); }
}

/* ---- the reader's toolbar, drawn tight on a phone ----
   The 44px above is a TAP TARGET, and it was doing double duty as spacing: seven
   glyphs each sitting in the middle of a 44px box, two more pixels of gap
   between them and a separator with 3px either side, read as seven scattered
   icons rather than one row of tools. Narrowed to 38px with the HEIGHT left at
   44 — a thumb lands on a row, and it is the vertical that has to survive.
   Seven of them then take 266px instead of 320, which on a 390px screen is the
   difference between a toolbar and a spread.

   PLACED AFTER THE pointer:coarse BLOCK ON PURPOSE. It narrows what that block
   set, both are single-class rules, and source order is the only thing deciding
   between them — above it, the 44px would simply win and this would do nothing. */
@media (max-width: 640px) {
  .mail-thread-tools { gap: 0; }
  .mail-tb { min-width: 38px; }
  .mail-tb-sep { margin: 0 2px; }
}

/* The keyboard hints are about a keyboard. Asked by INPUT rather than by width,
   because a 900px tablet has no keys either — the same reasoning as the splitter
   grip above, which a width query gets exactly wrong. */
@media (hover: none) { .mail-keys { display: none; } }

@media (max-width: 640px) {
  /* One pane at a time here, so there is nothing to split — and the inline
     flex-basis the splitter writes must not survive into a layout with no
     splitter in it, hence !important over an inline style. */
  .mail-splitter { display: none; }
  .mail-list-pane { flex: 1 1 auto !important; }
}

/* ---------- triage chips + daily briefing ---------- */
/* Deliberately the same weight and size as .sla-tag: they sit on the same row,
   and a badge that shouts louder than an SLA breach is telling the truth about
   its own importance and a lie about the workspace's. */
.tri-tag {
  font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 9px;
  letter-spacing: .2px;
}
.tri-interrupt { background: #fdeaea; color: var(--danger); }
.tri-later     { background: var(--accent-soft); color: var(--accent); }
.tri-ignore    { background: var(--ground); color: var(--ink-soft); }

.brief-wrap { flex: 1; overflow: auto; padding: 24px 16px; background: var(--ground); }
.brief-card {
  max-width: 720px; margin: 0 auto; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 22px 24px;
}
.brief-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.brief-card h2 { margin: 0; font-size: 19px; color: var(--ink); }
.brief-sub { margin: 4px 0 0; font-size: 13px; color: var(--ink-soft); }
.brief-narrative {
  margin: 16px 0 4px; font-size: 14px; line-height: 1.55; color: var(--ink);
  padding: 12px 14px; background: var(--accent-soft); border-radius: var(--radius);
}
.brief-items { margin-top: 14px; }

/* How much of the five is done. A bar rather than a number alone because the
   thing being reported is "nearly there", which is a length before it is a
   count. Replaced outright once the list is finished — a full bar and a "5 of 5"
   is a progress report about something that is no longer in progress. */
.brief-progress { display: flex; align-items: center; gap: 10px; margin-top: 14px; }
.brief-progress-bar {
  flex: 1 1 auto; height: 6px; border-radius: 3px; background: var(--ground); overflow: hidden;
}
.brief-progress-fill { height: 100%; background: var(--accent); border-radius: 3px; transition: width .18s ease; }
.brief-progress-text { flex: none; font-size: 12px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.brief-alldone {
  margin-top: 14px; padding: 10px 13px; border-radius: 9px;
  background: var(--accent-soft); color: var(--accent); font-size: 13.5px; font-weight: 600;
}

/* The kind headings. Faint and small: they are a label on a shelf, not an item
   on it, and five items under four headings must still read as five items. */
.brief-group + .brief-group { margin-top: 6px; }
.brief-group-head {
  display: flex; align-items: center; gap: 7px;
  padding: 12px 0 4px; font-size: 11px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-soft);
}
.brief-group-icon { font-size: 13px; letter-spacing: 0; }
/* Truncates rather than pushing the count off the row — "Conversations waiting"
   plus a badge does not fit 320px, and the number is the half worth keeping. */
.brief-group-label { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.brief-group-count {
  min-width: 18px; padding: 0 5px; border-radius: 9px; background: var(--ground);
  font-size: 10.5px; letter-spacing: 0; text-align: center; font-variant-numeric: tabular-nums;
}
/* The same badge, but it goes somewhere: the briefing shows five of twelve, and
   this is the other seven. A button rather than a link because the destination
   is a view switch, not a URL. */
button.brief-group-count.is-link {
  border: 0; font-family: inherit; font-weight: 700; color: var(--accent);
  background: var(--accent-soft); cursor: pointer; text-transform: none;
}
button.brief-group-count.is-link:hover { filter: brightness(0.96); }

.brief-item {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 12px 0; border-bottom: 1px solid var(--line);
}
.brief-item:last-child { border-bottom: 0; }

/* A real 24px target. This is the control the whole finish line rests on, and
   it is pressed with a thumb as often as with a mouse. */
.brief-check {
  flex: 0 0 22px; width: 22px; height: 22px; margin-top: 1px; padding: 0;
  border: 1.5px solid var(--line); border-radius: 50%; background: var(--panel);
  color: transparent; font-size: 12px; font-weight: 700; line-height: 1; cursor: pointer;
}
.brief-check:hover { border-color: var(--accent); }
/* The tick landing. Plays once per item — see briefJustDone() — because a list
   that silently rearranged itself while you looked away is one you re-read from
   the top every time. */
.brief-check.just-done { animation: tickPop .5s cubic-bezier(.2,1.4,.4,1); }
/* The same landing as tickPop, for the phone layout where the circle is a
   pseudo-element inside a bigger button — see the 640px block. */
@keyframes tickPopDot {
  0%   { transform: scale(.55); background: var(--panel); border-color: var(--line); }
  45%  { transform: scale(1.25); background: var(--accent); border-color: var(--accent); }
  100% { transform: scale(1); }
}
@keyframes tickPop {
  0%   { transform: scale(.55); background: var(--panel); border-color: var(--line); color: transparent; }
  45%  { transform: scale(1.25); background: var(--accent); border-color: var(--accent); color: #fff; }
  100% { transform: scale(1); }
}
/* Why it is ticked, when the world ticked it rather than the person. */
.brief-resolved {
  margin-inline-start: 7px; padding: 1px 6px; border-radius: 8px;
  background: var(--accent-soft); color: var(--accent);
  font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  vertical-align: 1px;
}
.brief-check:disabled { cursor: default; }

/* Both containers, one rule. The dock draws the same control and scoping this to
   .brief-item left its ticks invisible — a transparent glyph in an empty circle. */
.brief-item.is-done .brief-check,
.brief-dock-item.is-done .brief-check { background: var(--accent); border-color: var(--accent); color: #fff; }
/* Faded, not removed. A finished item that disappears takes the evidence of the
   work with it, and the count above stops being checkable against anything. */
.brief-item.is-done .brief-item-body { opacity: .5; }
.brief-item.is-done .brief-title { text-decoration: line-through; }

.brief-item-body { flex: 1; min-width: 0; }
.brief-title { font-size: 14px; font-weight: 600; color: var(--ink); }
/* Only tickets, alerts and leads carry this — see the comment in briefing.js.
   A waiting chat's context is on its inbox row already. */
.brief-detail { font-size: 12px; color: var(--ink); margin-top: 2px; }
.brief-why { font-size: 12px; color: var(--ink-soft); margin-top: 2px; }
.brief-action { font-size: 12px; color: var(--accent); margin-top: 3px; }
/* A draft is visibly UNSENT. The dashed edge and the label are the whole point:
   a suggestion that looks like a sent message is a suggestion somebody will
   believe went out. */
.brief-draft {
  margin-top: 8px; padding: 9px 11px; border: 1px dashed var(--line);
  border-radius: var(--radius); background: var(--note);
}
.brief-draft-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-bottom: 5px;
}
.brief-draft-label {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .6px;
  color: var(--warn);
}
/* The copy button must not shrink to nothing next to a long label, and it needs
   a finger-sized target on a phone even though .btn.xs is sized for a mouse. */
.brief-draft-head .btn { flex: 0 0 auto; min-height: 28px; }
/* Selectable on purpose: the copy button is the fast path, and highlight-and-copy
   is the one that still works when the clipboard API is unavailable — which is
   every phone testing against a laptop's LAN address over plain http. */
.brief-draft-text { user-select: text; -webkit-user-select: text; }

/* Settings: a checkbox row that lines up with .crm-field beside it. */
.ws-check {
  display: flex; align-items: center; gap: 9px; margin: 10px 0 4px;
  font-size: 13.5px; color: var(--ink); cursor: pointer;
}
.ws-check input { width: 16px; height: 16px; flex: 0 0 16px; accent-color: var(--accent); }
.ws-check input:disabled { cursor: default; }
.brief-draft-text { font-size: 13px; color: var(--ink); line-height: 1.5; white-space: pre-wrap; }
.brief-item-actions { display: flex; flex-direction: column; gap: 6px; flex: 0 0 auto; }
.brief-empty { padding: 26px 0; text-align: center; color: var(--ink-soft); font-size: 14px; }
/* The count on the rail's 🎯. Small and on the corner of the icon — the rail is
   a column of glyphs, so anything wider than this stops being a rail. */
.rail-btn { position: relative; }
.rail-count {
  position: absolute; top: 3px; inset-inline-end: calc(50% - 20px); min-width: 15px; height: 15px;
  padding: 0 3px; border-radius: 8px; background: var(--accent); color: #fff;
  font-size: 9.5px; font-weight: 700; line-height: 15px; text-align: center;
  font-variant-numeric: tabular-nums; pointer-events: none;
}

/* ----- the briefing, docked in the corner of the inbox -----
   Absolutely positioned so it costs the three columns nothing: #chatlist, #conv
   and #details are a flex row, and a fourth child in normal flow would become a
   fourth column and squeeze the conversation. */
.brief-dock {
  position: absolute; inset-inline-end: 14px; bottom: 14px; z-index: 40;
  width: 268px; max-width: calc(100% - 28px);
  display: flex; flex-direction: column;
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 8px 28px rgba(20, 32, 28, .14); overflow: hidden;
}
.brief-dock-head {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 8px 8px 11px; border-bottom: 1px solid var(--line); background: var(--ground);
}
.brief-dock.is-min .brief-dock-head { border-bottom: 0; }
.brief-dock-title {
  flex: 1 1 auto; min-width: 0; padding: 0; border: 0; background: none;
  font: inherit; font-size: 12.5px; font-weight: 700; color: var(--ink); cursor: pointer;
  text-align: start; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.brief-dock-title:hover { color: var(--accent); }
.brief-dock-left {
  flex: none; padding: 1px 7px; border-radius: 9px; background: var(--accent-soft);
  color: var(--accent); font-size: 10.5px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.brief-dock-btn {
  flex: none; width: 24px; height: 24px; padding: 0; border: 0; border-radius: 6px;
  background: none; color: var(--ink-soft); font: inherit; font-size: 11px; cursor: pointer;
}
.brief-dock-btn:hover { background: var(--panel); color: var(--ink); }
/* Collapsing hides the list and leaves the header, which is still a way back to
   the full briefing — a dock you can only close would make "get it out of the
   way for a minute" cost you the list. */
.brief-dock.is-min .brief-dock-items { display: none; }
.brief-dock-items { max-height: 250px; overflow-y: auto; padding: 4px 0; }
.brief-dock-item { display: flex; align-items: center; gap: 8px; padding: 3px 10px; }
.brief-dock-item .brief-check { flex: 0 0 18px; width: 18px; height: 18px; margin: 0; font-size: 10px; }
.brief-dock-open {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 7px;
  padding: 5px 7px; border: 0; border-radius: 7px; background: none;
  font: inherit; font-size: 12.5px; color: var(--ink); text-align: start; cursor: pointer;
}
.brief-dock-open:hover { background: var(--accent-soft); }
.brief-dock-open:disabled { color: var(--ink-soft); cursor: default; }
.brief-dock-open:disabled:hover { background: none; }
.brief-dock-ico { flex: none; font-size: 12px; }
.brief-dock-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.brief-dock-item.is-done .brief-dock-name { text-decoration: line-through; opacity: .5; }
/* Sits at the end of the row and stays quiet until the row is under the cursor —
   three controls on a 268px row is one too many to have all shouting. It is
   always present for a keyboard or a touch screen, just faint. */
.brief-dock-draft {
  flex: none; width: 26px; height: 26px; padding: 0; border: 0; border-radius: 7px;
  background: none; color: var(--ink-soft); font: inherit; font-size: 12px;
  cursor: pointer; opacity: .45;
}
.brief-dock-item:hover .brief-dock-draft, .brief-dock-draft:focus-visible { opacity: 1; }
.brief-dock-draft:hover { background: var(--accent-soft); color: var(--accent); }
.brief-dock-draft:disabled { opacity: 1; color: var(--accent); cursor: progress; }

@media (max-width: 640px) {
  /* A PHONE CANNOT HAVE THIS, AND PRETENDING OTHERWISE COSTS THE COMPOSER.
     Below this width the inbox shows one column at a time — the chat list and
     the conversation swap — so "the list stays visible while you act" is not a
     thing that can be true here. Inside a chat the dock would simply be a panel
     over the box you are typing in, so it goes; on the list, where there is room,
     it stays as its header, which is a way back to the briefing. */
  /* Bottom LEFT here. The new-chat button is fixed at right:16px bottom:74px on
     a phone, and the two of them at the same corner is a floating control
     covering a floating control. */
  .brief-dock { inset-inline-start: 10px; inset-inline-end: auto; bottom: 10px; width: auto; max-width: calc(100% - 20px); }
  .brief-dock .brief-dock-items { display: none; }
  .brief-dock .brief-dock-head { border-bottom: 0; }
  body.has-active-chat .brief-dock { display: none; }
}
.brief-foot { margin-top: 18px; font-size: 11px; color: var(--ink-soft); }

@media (max-width: 720px) {
  .brief-wrap { padding: 14px 10px; }
  .brief-card { padding: 16px 14px; border-radius: 10px; }
  .brief-head { flex-wrap: wrap; }
  .brief-card h2 { font-size: 17px; }
  .brief-narrative { font-size: 13.5px; padding: 11px 12px; }
  /* The actions drop UNDER the item rather than squeezing beside it. A 44px
     target is the floor for a thumb, and three of them beside a title on a
     375px screen leaves the title about forty pixels — so the row wraps and the
     buttons take the width they need. */
  .brief-item { flex-wrap: wrap; }
  .brief-item-actions {
    flex-direction: row; width: 100%; gap: 8px;
    margin-top: 8px; padding-inline-start: 34px;   /* clears the rank bubble + its gap */
  }
  .brief-item-actions .btn { flex: 1 1 0; min-height: 40px; font-size: 13px; }
  .brief-draft-head { flex-wrap: wrap; }
  .brief-draft-head .btn { min-height: 34px; padding: 4px 12px; }
  .brief-title { font-size: 13.5px; }

  /* TAP TARGETS, without redrawing anything.
     Measured on a 390px phone: the tick was 22x22, the "3 of 12" link 50x14 and
     the dock's collapse and close 24x24 — all of them things you press with a
     thumb, all of them well under the 44px a thumb actually covers. Growing the
     controls would work and would cost the layout: a 44px circle beside a title
     pushes that title into two lines on a 320px screen, and a taller badge grows
     every group heading.
     So the hit area grows and the drawing does not. A transparent ::after
     stretched past the element's own box is the whole trick — it is inside the
     button, so a press anywhere in it is a press on the button. */
  /* THE BUTTON STOPS BEING THE CIRCLE.
     A transparent ::after stretched past the button was the obvious way to grow
     the target and it does not work here: .brief-item-body is painted after the
     button, so on the side where there is room to grow, the body takes the taps.
     Giving the overlay a z-index would fix that and hand the first 20px of every
     title to the checkbox, which is worse than a small checkbox.
     So on a phone the button becomes a 40px target and the 22px circle is drawn
     inside it. Nothing about the appearance changes. */
  .brief-check {
    flex: 0 0 40px; width: 40px; height: 40px; margin: -9px -9px -9px -8px;
    border: 0; border-radius: 20px; background: none; position: relative; z-index: 0;
  }
  .brief-check::before {
    content: ''; position: absolute; left: 50%; top: 50%; z-index: -1;
    width: 22px; height: 22px; margin: -11px 0 0 -11px;
    border: 1.5px solid var(--line); border-radius: 50%; background: var(--panel);
  }
  .brief-item.is-done .brief-check,
  .brief-dock-item.is-done .brief-check { background: none; }
  .brief-item.is-done .brief-check::before,
  .brief-dock-item.is-done .brief-check::before { background: var(--accent); border-color: var(--accent); }
  /* The pop belongs to the circle now, not to the 40px box around it. */
  .brief-check.just-done { animation: none; }
  .brief-check.just-done::before { animation: tickPopDot .5s cubic-bezier(.2,1.4,.4,1); }

  .brief-group-count.is-link, .brief-dock-btn, .brief-dock-draft { position: relative; }
  /* Sideways more than vertically: the group heading is a row, and a tall
     overlay here would sit on top of the first item under it. Nothing is painted
     over these two, so the overlay works where it did not for the checkbox. */
  .brief-group-count.is-link::after { content: ''; position: absolute; inset: -14px -14px; }
  .brief-dock-btn::after, .brief-dock-draft::after { content: ''; position: absolute; inset: -9px; }
  /* Regenerate sits alone at the top of the card and was 26px tall. */
  .brief-head .btn { min-height: 38px; padding: 8px 14px; }
}
@media (max-width: 360px) {
  /* At 320 the left indent costs more than the alignment buys. */
  .brief-item-actions { padding-inline-start: 0; }
  /* The 40px target stays; only the drawn circle gives ground at 320. */
  .brief-check::before { width: 20px; height: 20px; margin: -10px 0 0 -10px; }
}

/* An announcement group we may not post to. WhatsApp shows a line of text where
   the composer would be; so do we, with the one thing WhatsApp has no concept
   of — a private note — still reachable beside it. */
.composer-locked {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px; color: var(--ink-soft); font-size: 13.5px;
}
.composer-locked-ico { font-size: 15px; opacity: .8; }
.composer-locked-text { flex: 1; min-width: 0; }
.composer-locked .note-toggle { flex: none; }

/* ---- Inline completion -------------------------------------------------- */
/*
 * The mirror, and why it is written this way.
 *
 * A textarea cannot hold styled text, so the grey remainder is drawn on a div
 * underneath it. That only works while the two boxes agree about where every
 * glyph lands — so every property that affects layout is declared ONCE, on both
 * selectors together. Restating them separately is how they drift, and a mirror
 * that drifts puts the suggestion in the wrong place, which reads as a bug
 * rather than as a feature with a rendering flaw.
 *
 * The selector is .composer .composer-ghost rather than .composer-ghost so it
 * carries the same specificity as .composer textarea. Equal specificity, one
 * declaration, no override to lose.
 */
.composer textarea,
.composer .composer-ghost {
  box-sizing: border-box;
  width: 100%;
  border: 1px solid transparent;
  border-radius: 22px;
  padding: 11px 16px;
  min-height: 44px;
  max-height: 130px;
  font: inherit;
  line-height: 1.4;
  letter-spacing: inherit;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  word-break: normal;
  text-align: start;
}
.composer .composer-ghost {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  color: transparent;
  margin: 0;
}
/* Invisible rather than absent: it exists only to push the grey half to exactly
   where the caret is, including after a line wrap. */
.composer .composer-ghost .cg-typed { visibility: hidden; }
.composer .composer-ghost .cg-rest { color: var(--ink-soft); opacity: .6; }
/* The wrap paints the background so the textarea can be transparent and let the
   mirror show through it.
 *
 * :focus and .note-mode have to be listed too, and forgetting them is exactly
 * how this failed the first time: the textarea's focus rule sets its own
 * background, it outranks a plain `.composer textarea`, and the composer is
 * focused for the whole time the ghost is meant to be readable. The result was
 * a completion that measured as visible in every way — real colour, real size,
 * `visibility: visible` — and was painted over by the box in front of it.
 */
.composer .composer-input-wrap { background: var(--ground); border-radius: 22px; }
.composer textarea,
.composer textarea:focus,
.composer textarea.note-mode { position: relative; z-index: 1; background: transparent; }
/* The focus tint moves to the wrap, so the box still lights up when you click
   into it and the mirror underneath is still legible. */
.composer .composer-input-wrap:focus-within { background: var(--panel); }
/* A note is a different kind of message and keeps its own colour — the ghost is
   switched off in note mode anyway, so nothing is hidden by this. */
.composer .composer-input-wrap:has(textarea.note-mode) { background: var(--note); }

/* A completion a model wrote, rather than one a colleague sent. Italic and a
   shade fainter: enough to register before Tab is pressed, not enough to turn
   the composer into a status display. */
.composer .composer-ghost .cg-rest.cg-made-up { font-style: italic; opacity: 0.8; }
/* An automatic label is a statement, not a question: no accent, and the only
   button is the way back. Kept in the same strip so "what did it decide?" and
   "what is it asking?" are read in one place. */
.lsug.is-applied { background: var(--ground); border-style: dashed; }
/* A control that is present but unavailable, with its reason beside it. Faded
   rather than hidden: an option that vanishes teaches nothing. */
.lb-auto.is-off { opacity: 0.65; }
/* The state of the rule that makes any of this run, reported beside the labels
   it governs. Clay when nothing is happening -- it is a dead end somebody needs
   to be told about -- and quiet green once it is working. */
.lb-rule { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 10px 0 4px;
  padding: 10px 12px; border-radius: 10px; font-size: 13px; line-height: 1.5; max-width: 720px; }
.lb-rule.off { background: #fdf6e3; border: 1px solid #e8d9a8; color: #7a5c11; }
.lb-rule.on { background: #f2f8f5; border: 1px solid #cfe3da; color: var(--ink-soft); }
.lb-rule > div { flex: 1 1 260px; min-width: 0; }
.lb-rule a { color: inherit; text-decoration: underline; }
/* How auto-labelling is doing. Plain numbers rather than a chart: five counts
   per label is a table, and a table is readable at a glance without a legend. */
.lb-stats { margin: 4px 0 12px; padding: 10px 12px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--panel); max-width: 720px; }
.lb-stats-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 8px; margin-bottom: 10px; font-size: 13px; }
.lb-stats-title { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
/* The window the numbers cover, said once and quietly, rather than repeated as
   a column heading. */
.lb-window { font-size: 11.5px; color: var(--ink-soft); }

/* The answer before the breakdown: the table says which label, this says
   whether any of it is working. */
.lb-summary { margin-bottom: 10px; }
.lb-summary-line { font-size: 13px; margin-bottom: 6px; }
.lb-summary-line b { font-variant-numeric: tabular-nums; }
/* A proportion, drawn rather than described. Two colours only — kept and not —
   because a stacked chart of five outcomes needs a legend and this does not. */
.lb-bar { height: 5px; border-radius: 999px; background: var(--ground); overflow: hidden; margin-bottom: 4px; }
.lb-bar span { display: block; height: 100%; background: var(--accent); border-radius: 999px; }

/* Numbers align on their digits, so a column can be read down rather than
   across. Zero is dimmed: it is the absence of a thing, not a measurement. */
.lb-stats-table td:not(:first-child) { text-align: end; font-variant-numeric: tabular-nums; width: 1%; white-space: nowrap; }
.lb-stats-table th:not(:first-child) { text-align: end; }
.lb-stats-table tbody tr:hover td, .lb-stats-table tr:hover td { background: var(--ground); }
.lb-zero { color: var(--ink-soft); opacity: 0.5; }

/* Waiting, with the shape of what is coming rather than a spinner. */
.lb-stats.is-loading { min-height: 74px; }
.lb-skel { height: 12px; border-radius: 6px; background: var(--ground); margin-bottom: 8px; }
.lb-skel.short { width: 45%; }
@media (prefers-reduced-motion: no-preference) {
  .lb-skel { animation: lb-pulse 1.4s ease-in-out infinite; }
  @keyframes lb-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
}

/* An empty state that says what will happen, not that nothing has. */
.lb-empty { font-size: 12.5px; line-height: 1.6; color: var(--ink-soft); }
.lb-empty b { color: var(--ink); }
.lb-stats-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.lb-stats-table th { text-align: start; font-weight: 600; color: var(--ink-soft); padding: 4px 6px;
  border-bottom: 1px solid var(--line); font-size: 11.5px; }
.lb-stats-table td { padding: 5px 6px; border-bottom: 1px solid var(--ground); }
.lb-stats-table tr:last-child td { border-bottom: none; }
/* The error signal, and the only number here worth colouring. */
.lb-bad { color: #8a2020; font-weight: 600; }

/* ---------- Settings → Webhooks ---------- */

/* The permanent line about what an endpoint can see. Deliberately styled as a
   notice rather than a hint: this is the sentence a customer's staff will be
   pointed at later, and a grey 12px aside is not one anybody reads. */
.wh-scope {
  border-inline-start: 3px solid var(--accent); background: var(--accent-soft);
  border-start-end-radius: 8px; border-end-end-radius: 8px; padding: 10px 12px; margin: 0 0 14px;
  font-size: 13px; line-height: 1.5; max-width: 720px;
}
.wh-new { margin: 0 0 14px; }
.wh-empty { padding: 16px 0; color: var(--ink-soft); font-size: 13px; }

.wh-card {
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
  padding: 12px 14px; margin-bottom: 10px; max-width: 860px;
}
.wh-card.off { opacity: 0.72; }
.wh-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.wh-name { display: flex; align-items: center; gap: 8px; min-width: 0; }
.wh-state { font-size: 11.5px; }
.wh-state.on { color: var(--accent); }
.wh-state.off { color: var(--warn); }
/* A row a Zap owns. Marked rather than hidden: it is delivering the workspace's
   conversations somewhere, so it belongs in the same list as the ones somebody
   pasted in — just without the buttons that would put the two systems out of
   step with each other. */
/* HubSpot sits above the webhook list because it is the answer for most people
   who opened this screen — they asked for HubSpot, and a webhook is the general
   tool they would otherwise have to build one out of. */
.wh-hs {
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
  padding: 14px 16px; margin-bottom: 16px; max-width: 860px;
}
.wh-hs.connected { border-color: var(--accent); }
.wh-hs-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.wh-hs p { margin: 0 0 10px; }
.wh-hs-opts { display: flex; flex-direction: column; gap: 8px; margin: 12px 0 4px; }
/* Connected, but every switch is off. Without this the screen looks identical
   to a working integration and the only difference is that nothing happens --
   which is exactly how somebody concludes it is broken and disconnects. */
.wh-idle {
  border-inline-start: 3px solid var(--warn); background: var(--note);
  border-start-end-radius: 8px; border-end-end-radius: 8px; padding: 10px 12px; margin: 12px 0 0;
  font-size: 13px; line-height: 1.5;
}

.wh-src {
  font-size: 11px; padding: 1px 7px; border-radius: 10px;
  background: #fff4e0; color: #8a5a12; border: 1px solid #f0dcb8;
}
.wh-acts { display: flex; gap: 6px; flex-wrap: wrap; }

/* A webhook URL is one long unbreakable string. It wraps rather than scrolling
   sideways, or the card grows a horizontal scrollbar on every phone. */
.wh-url { margin: 8px 0 6px; }
.wh-url code {
  display: block; font-family: ui-monospace, Menlo, monospace; font-size: 12px;
  background: var(--ground); border: 1px solid var(--line); border-radius: 6px;
  padding: 6px 8px; word-break: break-all;
}
.wh-meta { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 6px; }
.wh-ev, .wh-only {
  font-size: 11.5px; padding: 2px 7px; border-radius: 10px;
  border: 1px solid var(--line); color: var(--ink-soft); background: var(--ground);
}
.wh-only { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.wh-err {
  margin-top: 8px; padding: 8px 10px; border-radius: 8px; font-size: 12.5px;
  background: #fdeaea; color: #8a2020; border: 1px solid #f3caca;
}
.wh-err > div { margin-top: 6px; }

/* ---- the delivery log ---- */
.wh-log:empty { display: none; }
.wh-log { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 8px; }
.wh-log-head { font-size: 12px; color: var(--ink-soft); margin-bottom: 6px; }
.wh-log-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 12px; padding: 5px 0; border-bottom: 1px solid var(--line);
}
.wh-log-ev { font-family: ui-monospace, Menlo, monospace; min-width: 130px; }
.wh-log-st { font-weight: 600; min-width: 62px; }
.wh-log-st.delivered { color: var(--accent); }
.wh-log-st.failed { color: var(--danger); }
.wh-log-st.pending { color: var(--warn); }
.wh-log-at { color: var(--ink-soft); }
.wh-log-err { color: var(--danger); flex: 1 1 100%; word-break: break-word; }

/* ---- the form ---- */
.wh-form {
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
  padding: 14px 16px; margin-bottom: 14px; max-width: 720px;
}
.wh-form h3 { margin: 0 0 12px; font-size: 15px; }
.wh-f { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
.wh-f > span { font-size: 12.5px; color: var(--ink-soft); }
.wh-f input {
  border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px;
  font: inherit; font-size: 13.5px; background: var(--panel); color: var(--ink);
}
.wh-f input:focus { outline: 2px solid var(--accent-soft); border-color: var(--accent); }
.wh-f-note { font-size: 12px; color: var(--ink-soft); margin: -4px 0 12px; }
.wh-f-sec {
  font-size: 12.5px; font-weight: 600; margin: 14px 0 7px;
  padding-top: 10px; border-top: 1px solid var(--line);
}
.wh-evs { display: flex; flex-direction: column; gap: 2px; margin-bottom: 4px; }
.wh-ev-row {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 5px 6px; border-radius: 6px; cursor: pointer; font-size: 13px;
}
.wh-ev-row:hover { background: var(--ground); }
.wh-ev-name { font-weight: 500; }
.wh-ev-note { font-size: 11.5px; color: var(--ink-soft); }
.wh-labels { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.wh-lb { display: flex; align-items: center; gap: 6px; font-size: 13px; cursor: pointer; }
.wh-f-acts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }

.wh-secret {
  margin-top: 12px; border: 1px solid var(--warn); border-radius: 10px;
  padding: 12px 14px; background: var(--note);
}
.wh-secret p { margin: 0 0 8px; font-size: 13px; }
.wh-secret code { word-break: break-all; }

/* ---------- Settings → Language ---------- */

/* Five rows, each a whole target rather than a radio with a label beside it:
   the language name is the thing people aim at, and a 12px circle is not. */
.lang-list { display: flex; flex-direction: column; gap: 6px; margin: 12px 0 16px; max-width: 480px; }
.lang-row {
  display: flex; align-items: baseline; gap: 10px; cursor: pointer;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel);
}
.lang-row:hover { background: var(--ground); }
.lang-row.active { border-color: var(--accent); background: var(--accent-soft); }
.lang-row:focus-within { outline: 2px solid var(--accent-soft); }

/* The name of the language, written in that language, at the size of something
   you read rather than something you scan past. dir is set per row in the
   markup so العربية renders correctly on a left-to-right page. */
.lang-name { font-size: 15px; font-weight: 500; }
/* The same language named in English, quietly. It is the row an admin uses to
   help somebody over the phone -- "the one that says Arabic". */
.lang-en { font-size: 12px; color: var(--ink-soft); }
.lang-rtl {
  font-size: 10.5px; letter-spacing: 0.03em; padding: 1px 6px; border-radius: 999px;
  border: 1px solid var(--line); color: var(--ink-soft); margin-inline-start: auto;
}
/* Said once, in the language being reviewed, so the person who can spot the
   mistakes is the person who reads it. */
.lang-review { border-inline-start: 3px solid var(--warn); padding-inline-start: 10px; }

@media (pointer: coarse) {
  .lang-row { padding: 13px 12px; }
  .lang-row input[type=radio] { width: 20px; height: 20px; }
}

/* ============================================================================
   Right to left
   ============================================================================

   Almost all of this is handled above rather than here: the stylesheet uses
   logical properties throughout -- margin-inline-start, inset-inline-end,
   text-align: start -- so setting dir="rtl" on <html> mirrors the layout with
   no second stylesheet and no duplicated rules. i18n.js sets that attribute
   from the chosen language.

   What is left here is the handful of things a logical property cannot say. */

/* Which way "forward" points on the x axis.
 *
 * transform is PHYSICAL and has no logical form: translateX(24px) moves right
 * in Arabic exactly as it does in English, so a panel that slid in from the
 * screen edge would slide in from the wrong one. Multiplying by this flips the
 * two transforms that mean "toward the far edge" without a second copy of the
 * rule or of the keyframe.
 *
 * Centring transforms deliberately do NOT use it. translateX(-50%) against
 * left: 50% centres an element in both directions, and "flipping" it would push
 * a toast off the side of the screen. */
:root { --flip: 1; }
[dir="rtl"] { --flip: -1; }

/* ---- glyphs that encode a direction ----
 *
 * A back arrow points the way you came from, which in Arabic is the other way.
 * These are icon-only controls, so mirroring the whole element is safe and is
 * one rule instead of a translated string per arrow.
 *
 * Deliberately NOT applied to the pagination buttons, which read "« First" and
 * "Last »": the glyph sits beside a word there, mirroring would reverse the
 * word with it, and the browser's own bidi reordering already puts them in a
 * sensible order. */
[dir="rtl"] .conv-back,
[dir="rtl"] .nc-back,
[dir="rtl"] .details-close,
[dir="rtl"] .mail-splitter-chev,
[dir="rtl"] .det-caret {
  display: inline-block;
  transform: scaleX(-1);
}

/* ---- Latin inside Arabic ----
 *
 * THE DETAIL EVERY RTL RELEASE GETS WRONG. A phone number is a run of Latin
 * digits inside a right-to-left paragraph, and the bidi algorithm reorders the
 * two around each other: +971 50 123 4567 renders with the + at the wrong end,
 * or the country code swapped to the back, and nobody can tell whether it is a
 * display bug or the wrong number. The same goes for an email address, a
 * timestamp, an id, and anything monospaced.
 *
 * isolate says "this run has its own direction; do not let it argue with the
 * paragraph around it". */
[dir="rtl"] code,
[dir="rtl"] .msg-time,
[dir="rtl"] .tp-msg-time,
[dir="rtl"] .contact-number,
[dir="rtl"] .mail-addr,
[dir="rtl"] input[type="tel"],
[dir="rtl"] input[type="email"],
[dir="rtl"] input[type="url"] {
  direction: ltr;
  unicode-bidi: isolate;
  text-align: start;
}

/* ---- the two directional transforms ---- */
/* The ticket panel slides in from whichever edge it is docked to. */
@keyframes tpSlideFlip {
  from { transform: translateX(calc(24px * var(--flip))); opacity: 0.4; }
  to { transform: translateX(0); opacity: 1; }
}
[dir="rtl"] .ticket-panel { animation-name: tpSlideFlip; }
/* The toggle knob travels toward the end of its track. */
[dir="rtl"] .switch input:checked + .slider:before { transform: translateX(-18px); }

/* ---- shadows, which also have an x axis ----
 *
 * A panel docked to the end of the screen casts its shadow inward. Both of
 * these are negative -- pointing left, away from a right-hand edge -- so both
 * point the wrong way once the panel is on the left. */
[dir="rtl"] .ticket-panel { box-shadow: 16px 0 40px rgba(20,28,25,0.16); }
[dir="rtl"] body.m-details #details { box-shadow: 14px 0 34px rgba(20, 28, 25, 0.16); }

/* ---- gradients, which are drawn in physical directions ---- */
/* Fade cues at the ends of the horizontally scrolling filter row. `to right`
   means "to the right" in every language, so the two swap. */
[dir="rtl"] .mail-filter-wrap::before { background: linear-gradient(to left, var(--panel), transparent); }
[dir="rtl"] .mail-filter-wrap::after { background: linear-gradient(to right, var(--panel), transparent); }

/* ================= CALENDAR =================
 *
 * Three modes over one layout: a fixed filter rail on the inline-start edge,
 * and a scrolling grid beside it. Day and week share the SAME grid — a week is
 * seven of what a day is one of — so there is one set of cell rules here rather
 * than two that drift.
 *
 * Sizing is in `fr` and `minmax` rather than fixed widths, because the column
 * count changes with the mode and a seven-column week has to survive a 1024px
 * laptop without a horizontal scrollbar.
 */
.cal-wrap { flex: 1; display: flex; min-width: 0; min-height: 0; }

/* ---- side filters ---- */
.cal-side {
  flex: 0 0 216px; min-width: 0; overflow-y: auto;
  padding: 14px; background: var(--panel);
  border-inline-end: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 12px;
}
.cal-side-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  font-weight: 700; font-size: 13px;
}
.cal-clear {
  border: none; background: none; padding: 0; color: var(--accent);
  font-size: 12px; font-weight: 600; text-decoration: underline;
}
.cal-f { display: flex; flex-direction: column; gap: 4px; }
.cal-f > span { font-size: 11.5px; font-weight: 600; color: var(--ink-soft); }
.cal-f select {
  width: 100%; max-width: 100%; padding: 6px 8px; border-radius: 6px;
  border: 1px solid var(--line); background: var(--panel);
}
.cal-check { display: flex; align-items: center; gap: 7px; font-size: 13px; }
.cal-legend {
  margin-top: auto; padding-top: 12px; border-top: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 5px;
  font-size: 11.5px; color: var(--ink-soft);
}
.cal-legend span { display: flex; align-items: center; gap: 7px; }
.cal-dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: 0 0 9px; }
.cal-dot.wa { background: var(--accent); }
.cal-dot.mail { background: var(--info); }
.cal-dot.ticket { background: var(--warn); }
.cal-dot.sched { background: #7c5cd6; }
/* Direction, in the same grammar the blocks use: soft is received, solid is
   sent. Without this line in the legend the fills are a code only the author
   of the stylesheet could read. */
.cal-swatch {
  width: 16px; height: 10px; border-radius: 3px; display: inline-block; flex: 0 0 16px;
  border: 1px solid var(--line);
}
.cal-swatch.in { background: var(--accent-soft); border-color: #bcd9cc; }
.cal-swatch.out { background: var(--accent); border-color: #0a5539; }

/* ---- header ---- */
.cal-main { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.cal-head {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; padding: 14px 20px;
  background: var(--panel); border-bottom: 1px solid var(--line);
}
.cal-head-left { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
.cal-head h2 { margin: 0; font-size: 19px; letter-spacing: -0.01em; }
.cal-tz {
  font-size: 11.5px; color: var(--ink-soft); background: var(--ground);
  border: 1px solid var(--line); border-radius: 999px; padding: 2px 9px; white-space: nowrap;
}
.cal-head-right { display: flex; align-items: center; gap: 10px; }
.cal-nav { display: flex; align-items: center; gap: 4px; }
.cal-arrow { min-width: 32px; padding: 4px 9px; font-size: 15px; line-height: 1; }
/* A chevron encodes a direction, so it has to turn round in Arabic — the same
   treatment .conv-back and friends already get. */
[dir="rtl"] .cal-arrow { display: inline-block; transform: scaleX(-1); }
.cal-modes { display: flex; background: var(--ground); border-radius: 8px; padding: 2px; }
.cal-mode {
  border: none; background: none; padding: 5px 14px; border-radius: 6px;
  font-size: 13px; font-weight: 600; color: var(--ink-soft);
}
.cal-mode.active { background: var(--panel); color: var(--ink); box-shadow: 0 1px 2px rgba(0,0,0,.08); }

/* ---- body ---- */
.cal-body { flex: 1; overflow: auto; min-height: 0; padding: 0 0 24px; }
.cal-empty { height: 260px; }
.cal-trunc {
  margin: 12px 20px 0; padding: 8px 12px; border-radius: 6px;
  background: #fdf3d0; border: 1px solid #eddca4; font-size: 12.5px;
}

/* ---- day summary strip ---- */
.cal-summary {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 14px 20px; border-bottom: 1px solid var(--line); background: var(--panel);
}
.cal-sum-card {
  min-width: 96px; padding: 8px 14px; border-radius: 8px;
  background: var(--ground); border: 1px solid var(--line);
}
.cal-sum-n { font-size: 20px; font-weight: 700; line-height: 1.1; }
.cal-sum-l { font-size: 11.5px; color: var(--ink-soft); }
.cal-sum-people { display: flex; flex-wrap: wrap; gap: 6px; flex: 1 1 240px; min-width: 0; }
.cal-person {
  font-size: 12px; padding: 3px 9px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent); white-space: nowrap;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.cal-person.more { background: var(--ground); color: var(--ink-soft); }

/* ---- the day/week grid ----
 *
 * One grid template drives both: --cal-cols is 1 for a day and 7 for a week,
 * and every row (the head and each hour) uses the same track list, which is
 * what keeps the column edges lined up between them. */
.cal-grid { --cal-cols: 7; --cal-gutter: 56px; }
.cal-grid-1 { --cal-cols: 1; }
.cal-grid-head,
.cal-row {
  display: grid;
  grid-template-columns: var(--cal-gutter) repeat(var(--cal-cols), minmax(0, 1fr));
}
.cal-grid-head {
  position: sticky; top: 0; z-index: 2;
  background: var(--panel); border-bottom: 1px solid var(--line);
}
.cal-col-head {
  padding: 7px 4px; text-align: center; border-inline-start: 1px solid var(--line);
  background: none; border-top: none; border-bottom: none; border-inline-end: none;
  display: flex; flex-direction: column; gap: 1px;
}
.cal-col-dow { font-size: 11px; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .04em; }
.cal-col-num { font-size: 15px; font-weight: 700; }
.cal-col-head.is-today .cal-col-num { color: var(--accent); }
.cal-row { border-bottom: 1px solid var(--line); min-height: 44px; }
.cal-gutter {
  padding: 4px 8px; text-align: end; font-size: 11px; color: var(--ink-soft);
  border-inline-end: 1px solid var(--line); white-space: nowrap;
}
.cal-cell {
  padding: 3px; border-inline-start: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
}

/* ---- a block ---- */
.cal-block {
  display: flex; align-items: flex-start; gap: 6px; width: 100%; min-width: 0;
  text-align: start; padding: 4px 7px; border-radius: 5px;
  border: 1px solid var(--line); background: var(--panel);
  border-inline-start: 3px solid var(--accent);
  font-size: 12px; line-height: 1.35;
}
.cal-block:hover { background: var(--ground); }
.cal-block.mail { border-inline-start-color: var(--info); }
.cal-block.ticket { border-inline-start-color: var(--warn); }
/* A queued send is the one block that has not happened yet, so it is drawn as a
   plan rather than a record: dashed, and in the accent rather than filled. */
.cal-block.sched {
  border-inline-start-color: #7c5cd6;
  border-style: dashed;
  border-inline-start-style: solid;
}
/* ---- which channel, and which way ----
 *
 * ONE HUE PER CHANNEL, TWO WEIGHTS PER DIRECTION. Green is WhatsApp, blue is
 * mail — the spine already said so and that reads well, so the fill says it
 * too. Within a hue, WEIGHT carries direction: received is the soft tint,
 * sent is the solid colour with the text knocked out white.
 *
 * The pairing matters more than either half. A block is read as a pair —
 * "green, and solid" — and light-vs-solid inside one hue is a far bigger step
 * than the two pastels this went through before it: --accent-soft against
 * --accent is 228→15 in red, not 228→220. Nothing here is a new colour: the
 * greens are the brand accent, the blues are --info, and --info-soft was added
 * beside --accent-soft so mail has the pair green already had.
 *
 * SENT ALSO MOVES ITS SPINE to the trailing edge — position saying the same
 * thing as weight, so neither cue carries direction alone, and being written in
 * logical properties it lands on the left in Arabic exactly as the transcript's
 * outbound bubbles do.
 *
 * A QUEUED SEND KEEPS ITS DASHED PURPLE. It is the one block that has not
 * happened yet, and it says "Queued to …" in words.
 *
 * AN INTERNAL NOTE TAKES NEITHER WEIGHT. It was never sent and never received,
 * so it keeps the note yellow the composer and the transcript already use. */
.cal-block.in { background: var(--accent-soft); }
.cal-block.in:hover { background: #d5eae0; }
.cal-block.mail.in { background: var(--info-soft); }
.cal-block.mail.in:hover { background: #d7e2f9; }

.cal-block.out:not(.sched) {
  background: var(--accent);
  color: #fff;
  border-block-color: #0a5539;
  border-inline-start-width: 1px; border-inline-start-color: #0a5539;
  border-inline-end: 3px solid #0a5539;
}
.cal-block.out:not(.sched):hover { background: #0b6344; }
.cal-block.mail.out:not(.sched) {
  background: var(--info);
  border-block-color: #1f4c9c;
  border-inline-start-color: #1f4c9c; border-inline-end-color: #1f4c9c;
}
.cal-block.mail.out:not(.sched):hover { background: #2a60c0; }
/* Secondary text on a solid card: white at 85%, so the hierarchy inside the
   block survives the inversion instead of flattening into one bright line. */
.cal-block.out:not(.sched) .cal-b-time,
.cal-block.out:not(.sched) .cal-b-prev,
.cal-block.out:not(.sched) .cal-b-dir,
.cal-block.out:not(.sched) .cal-b-tally { color: rgba(255, 255, 255, .85); }
.cal-block.out:not(.sched) .cal-b-who { color: #fff; }
.cal-block.out:not(.sched) .cal-b-unread { background: #fff; }

.cal-block.note {
  background: var(--note);
  border-block-color: #e8c65a; border-inline-end-color: #e8c65a;
}
.cal-block.note:hover { background: #f8e9b8; }

/* ---- unread ----
 *
 * Was a background wash in --accent-soft, which is now the RECEIVED fill — the
 * two could not both have it, and direction is the thing this screen exists to
 * show. So unread became a mark rather than a wash. That is also the honest
 * size for the fact: unread is a property of the CONVERSATION, not of this
 * message, and it had no business repainting a whole row. Paired with the 48h
 * window in routes/calendar.js, the dot now appears only where it means "still
 * waiting on you". */
.cal-b-unread {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); margin-inline-end: 5px; vertical-align: middle;
}

/* Vertical arrows, so they survive the RTL mirror with their meaning intact. */
.cal-b-dir {
  flex: 0 0 auto; font-size: 11px; padding-top: 1px; color: var(--ink-soft);
  direction: ltr; unicode-bidi: isolate;
}
/* The speaker: its own line, one line, so a group block reads
   group / who / what, and the name can never push the message out. */
.cal-b-who {
  display: block; font-weight: 600; font-size: 11.5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; unicode-bidi: plaintext;
}

/* ---- a ticket: how urgent, and how far along ----
 *
 * Two facts again, and again they get separate ink. The SPINE keeps priority —
 * amber, red once it is high or urgent — and the FILL takes status, in the same
 * grammar the messages above use: soft means waiting, solid means happening,
 * grey means done. So an urgent ticket still shouts from its spine whether it
 * has been started or not, and a closed one stops competing for attention
 * without disappearing from the record of the day.
 *
 * CLOSED IS ALSO STRUCK THROUGH, because "done" is the one status that must
 * survive being printed, screenshotted, or read by somebody who does not see
 * the grey. */
.cal-block.ticket { background: var(--warn-soft); }
.cal-block.ticket:hover { background: #fbe8ca; }
.cal-block.ticket.st-in_progress {
  background: var(--warn); color: #fff;
  border-block-color: #a85c05; border-inline-end-color: #a85c05;
}
.cal-block.ticket.st-in_progress:hover { background: #c26b06; }
.cal-block.ticket.st-in_progress .cal-b-time,
.cal-block.ticket.st-in_progress .cal-b-text { color: rgba(255, 255, 255, .9); }
.cal-block.ticket.st-closed {
  background: var(--ground); color: var(--ink-soft);
  border-block-color: var(--line); border-inline-end-color: var(--line);
}
.cal-block.ticket.st-closed:hover { background: #ecefee; }
.cal-block.ticket.st-closed .cal-b-text { text-decoration: line-through; }

/* The status in words, because three fills are three fills and only the word
   says which one is which. Same wording as the board's column headings. */
.cal-b-st {
  margin-inline-start: auto; flex: 0 0 auto;
  font-size: 10px; font-weight: 700; white-space: nowrap;
  padding: 1px 7px; border-radius: 999px;
  background: rgba(30, 43, 38, .07); color: var(--ink-soft);
}
.cal-block.st-in_progress .cal-b-st { background: rgba(255, 255, 255, .22); color: #fff; }

/* ---- labels ----
 *
 * In the label's OWN colour, which is the language every other surface already
 * speaks; labelTextColor() picks the readable text against it, same call the
 * details panel makes. A dot where the column is narrow, a named chip where
 * there is room — the name is always in the DOM for the tooltip and for a
 * screen reader, and CSS decides whether it is drawn. */
.cal-b-labels {
  margin-inline-start: auto; flex: 0 0 auto;
  display: flex; gap: 3px; align-items: center;
}
.cal-b-st + .cal-b-labels { margin-inline-start: 5px; }
.cal-b-lbl {
  width: 8px; height: 8px; border-radius: 999px;
  background: var(--lc, var(--ink-soft));
}
.cal-b-lbl i { display: none; }
.cal-b-lbl.more { background: var(--line); }
.cal-grid-1 .cal-b-lbl,
.cal-agenda .cal-b-lbl,
.cal-hover .cal-b-lbl {
  width: auto; height: auto; padding: 1px 7px;
  font-size: 10px; font-weight: 700; color: var(--tc, var(--ink));
}
.cal-grid-1 .cal-b-lbl i,
.cal-agenda .cal-b-lbl i,
.cal-hover .cal-b-lbl i { display: inline; font-style: normal; }
.cal-grid-1 .cal-b-lbl.more,
.cal-agenda .cal-b-lbl.more,
.cal-hover .cal-b-lbl.more { color: var(--ink-soft); }
/* The split inside a folded hour: ↓3 ↑2 rather than a bare "5 messages". */
.cal-b-tally {
  margin-inline-start: auto; flex: 0 0 auto; font-size: 11px; font-weight: 400;
  color: var(--ink-soft); font-variant-numeric: tabular-nums;
  direction: ltr; unicode-bidi: isolate;
}
.cal-block.pri-urgent, .cal-block.pri-high { border-inline-start-color: var(--danger); }
.cal-b-time { font-variant-numeric: tabular-nums; color: var(--ink-soft); flex: 0 0 auto; font-size: 11px; padding-top: 1px; }
.cal-b-icon { flex: 0 0 auto; font-size: 11px; padding-top: 1px; }
.cal-b-body { min-width: 0; display: flex; flex-direction: column; }
.cal-b-title { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cal-b-prev { color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* A WEEK COLUMN GETS TWO LINES OF MESSAGE, not one. One line at 46px was five
   words at best, and once the speaker's name was in front of them it was none:
   every sent block read "WhatsApp (pho…" and nothing else. Two lines is still
   a preview, but it is a preview of the MESSAGE. The title above it stays on
   one line — it is the thing the card repeats in full on hover. */
.cal-grid:not(.cal-grid-1) .cal-b-prev {
  white-space: normal; display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; overflow: hidden; text-overflow: clip; overflow-wrap: break-word;
}
.cal-block:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.cal-block.out:not(.sched):focus-visible { outline-color: #0a5539; box-shadow: 0 0 0 3px #fff inset; }
.cal-b-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-b-media { margin-inline-end: 3px; }
.cal-block-many { background: var(--ground); font-weight: 600; }

/* ---- the hover card ----
 *
 * One element, body-level, refilled per block (see bindCalHover). It takes no
 * pointer events on purpose: the block underneath stays the target, so the
 * click still opens the conversation and the card cannot trap the mouse. */
.cal-hover {
  position: fixed; z-index: 80; width: 300px; max-width: calc(100vw - 16px);
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 10px 30px rgba(20, 28, 25, .18);
  padding: 10px 12px; font-size: 13px; line-height: 1.4;
  pointer-events: none; opacity: 0; transition: opacity .12s ease;
}
.cal-hover.show { opacity: 1; }
.cal-h-head {
  display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  font-size: 11.5px; font-weight: 600; color: var(--ink-soft); margin-bottom: 4px;
}
.cal-h-chan.in { color: var(--accent); }
.cal-h-chan.out { color: #0a5539; }
.cal-h-chan.note { color: #8a6a0c; }
.cal-h-time { font-variant-numeric: tabular-nums; direction: ltr; unicode-bidi: isolate; flex: 0 0 auto; }
.cal-h-title { font-weight: 700; font-size: 14px; overflow-wrap: anywhere; unicode-bidi: plaintext; }
.cal-h-meta { color: var(--ink-soft); font-size: 12px; margin-top: 2px; unicode-bidi: plaintext; }
.cal-h-body {
  margin-top: 6px; white-space: pre-wrap; overflow-wrap: anywhere; unicode-bidi: plaintext;
  max-height: 7.2em; overflow: hidden;
}
.cal-hover .cal-b-labels { margin: 8px 0 0; flex-wrap: wrap; }
/* The picture in the hover card. A FIXED height, set before the image exists:
   the card is positioned by its size when it is shown, and an image that grew
   it afterwards would undo the clamp that kept it on screen. Until the image
   lands the box shows the old "📎 image" line, centred, as its placeholder. */
.cal-h-img {
  margin-top: 8px; height: 150px; border-radius: 8px; overflow: hidden;
  background: var(--ground); color: var(--ink-soft); font-size: 12px;
  display: flex; align-items: center; justify-content: center;
}
.cal-h-img img { width: 100%; height: 100%; display: block; object-fit: cover; }
/* A sticker is drawn whole, like the bubble does it -- cropping one cuts the
   joke off, and it has its own transparent background. */
.cal-h-img.sticker { background: none; }
.cal-h-img.sticker img { object-fit: contain; }
/* Failed: back to the one-line text the card always had. */
.cal-h-img.failed {
  height: auto; background: none; border-radius: 0;
  justify-content: flex-start; margin-top: 6px; font-size: 13px; color: inherit;
}
.cal-h-hint { display: block; margin-top: 8px; font-size: 11px; color: var(--ink-soft); }

/* ---- bidi inside a block ----
 *
 * Two different problems, and they do NOT take the same fix.
 *
 * A CLOCK TIME is always Latin digits, so it gets the isolate treatment the
 * numbers and addresses above already get: its own direction, settled, never
 * arguing with the Arabic around it.
 *
 * A CHAT NAME OR A SUBJECT is user content and may be in either script — "Dubai
 * Metals" one row, an Arabic company name the next. Forcing ltr would fix the
 * first and break the second. `plaintext` is the rule for exactly this case:
 * each run takes its direction from its own first strong character, so both
 * read correctly AND the ellipsis lands at the end of the text rather than the
 * end of the paragraph. Without it, a Latin title in an Arabic interface
 * truncates from the FRONT — "…bai Metals" — which reads as a different chat. */
.cal-b-time { direction: ltr; unicode-bidi: isolate; text-align: start; }
.cal-b-title, .cal-b-prev, .cal-b-text { unicode-bidi: plaintext; }
.cal-collapse {
  align-self: flex-start; border: none; background: none; padding: 1px 4px;
  font-size: 11px; color: var(--accent); text-decoration: underline;
}

/* ---- drag to reschedule ----
 *
 * Only the two kinds that are a PLAN carry the grab cursor. A received message
 * has no .movable class and so keeps the ordinary pointer: the affordance is
 * the honest signal about what can be moved, and offering it on a block that
 * would be refused is worse than not offering it at all. */
.cal-block.movable { cursor: grab; }
.cal-block.movable:active { cursor: grabbing; }
.cal-block.dragging { opacity: .4; }
/* The drop target is the CELL, so it lights up rather than the block — the
   question during a drag is "where will this land", not "what am I holding". */
.cal-cell.drop-target,
.cal-mday.drop-target {
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 2px var(--accent);
}
.cal-tip {
  margin: 10px 0 0; font-size: 11px; line-height: 1.45; color: var(--ink-soft);
}

/* A single day gets one wide column, so its blocks can afford two lines.
   Capped rather than full-bleed: a one-line message stretched across 1400px of
   screen is harder to read than the same message in a column, and the ragged
   right edge is what makes a timeline scannable. */
.cal-grid-1 .cal-b-prev { white-space: normal; }
.cal-grid-1 .cal-block { font-size: 13px; padding: 6px 10px; max-width: 720px; }

/* ---- month ---- */
.cal-month { padding: 14px 20px; }
.cal-month-head,
.cal-month-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; }
.cal-month-head { margin-bottom: 6px; }
.cal-month-head div {
  text-align: center; font-size: 11px; font-weight: 700; color: var(--ink-soft);
  text-transform: uppercase; letter-spacing: .05em;
}
.cal-mday {
  min-height: 92px; padding: 7px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--panel);
  display: flex; flex-direction: column; gap: 5px; align-items: stretch;
  text-align: start;
}
.cal-mday:hover { border-color: var(--accent); }
.cal-mday.out { opacity: .45; }
.cal-mday.quiet { background: var(--ground); }
.cal-mday.is-today { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.cal-mday-num { font-size: 13px; font-weight: 700; }
.cal-mday.is-today .cal-mday-num { color: var(--accent); }
.cal-pills { display: flex; flex-direction: column; gap: 3px; }
.cal-pill {
  font-size: 11px; font-weight: 600; padding: 1px 7px; border-radius: 999px;
  align-self: flex-start; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-pill.wa { background: var(--accent-soft); color: var(--accent); }
.cal-pill.mail { background: #e4effc; color: var(--info); }
.cal-pill.ticket { background: #fdf3d0; color: #92650a; }
.cal-pill.sched { background: #ece6fa; color: #5b3fb0; }
.cal-dots { display: flex; gap: 3px; margin-top: auto; flex-wrap: wrap; }

/* ---- narrow desktop ---- */
@media (max-width: 1100px) {
  .cal-side { flex-basis: 180px; }
  .cal-grid { --cal-gutter: 44px; }
  .cal-b-time { display: none; }
}

/* ---- the calendar at phone width ----
 *
 * Phase 1 hid this below 640px: a seven-column hour grid at 375px is 46px per
 * column, narrower than the time printed in a block. The grid was the problem,
 * not the calendar — so week now renders as the agenda list (calBodyHtml
 * decides), and what is left to do here is the chrome around it.
 *
 * THE FILTER PANEL BECOMES A DRAWER. Stacked above the grid it pushed the
 * calendar off the screen — the panel is taller than the thing it filters, so
 * the first paint was all controls and no content. Off-canvas costs nothing
 * until it is opened.
 */
@media (max-width: 640px) {
  .cal-wrap { position: relative; }

  /* Off-canvas, and OUT of the flow entirely — a transform alone would leave
     the column occupying width and squeeze the grid it slid away from. */
  .cal-side {
    position: fixed; inset-block: 50px 58px;   /* under the topbar, above the rail */
    inset-inline-end: 0; z-index: 45;
    flex: 0 0 auto; width: min(320px, 86vw); max-width: 86vw;
    border-inline-end: none; border-inline-start: 1px solid var(--line);
    box-shadow: -6px 0 24px rgba(20, 28, 25, 0.18);
    transform: translateX(100%);
    transition: transform .2s ease;
    overscroll-behavior: contain;
  }
  [dir="rtl"] .cal-side { transform: translateX(-100%); }
  .cal-wrap.drawer-open .cal-side { transform: none; }

  .cal-backdrop {
    position: fixed; inset: 50px 0 58px; z-index: 44;
    background: rgba(0, 0, 0, .38);
    opacity: 0; pointer-events: none; transition: opacity .2s ease;
  }
  .cal-wrap.drawer-open .cal-backdrop { opacity: 1; pointer-events: auto; }

  /* A drawer with no way out but the backdrop is one people close by accident
     and cannot close on purpose. */
  .cal-drawer-done { display: block; width: 100%; margin-block-start: 12px; min-height: 44px; }
  .cal-filter-btn { display: inline-flex; align-items: center; gap: 6px; }
  .cal-filter-btn.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
  .cal-filter-n {
    min-width: 18px; height: 18px; border-radius: 9px; padding: 0 5px;
    display: inline-grid; place-items: center;
    background: var(--accent); color: #fff; font-size: 11px; font-weight: 700;
  }

  /* The header loses its second row: the title wraps under the controls rather
     than sitting beside them, which is what keeps the arrows and the modes on
     one line at 360px. */
  /* THREE SHORT ROWS BEAT ONE THAT OVERFLOWS.
     Nav + Filters + four modes on one line wants ~560px; at 390 it pushed the
     whole page wider than the viewport, which cut the modes AND every block in
     the list. Wrapping is what keeps the content inside the screen. */
  .cal-head { padding: 10px 12px; gap: 8px; }
  .cal-head h2 { font-size: 16px; }
  .cal-head-left { flex: 1 1 100%; order: -1; min-width: 0; }
  .cal-head-right { flex: 1 1 100%; flex-wrap: wrap; gap: 8px; min-width: 0; }
  .cal-nav { flex: 0 0 auto; }
  /* Pushed to the end of its row, so the two controls that open something sit
     at opposite edges rather than colliding in the middle. */
  .cal-filter-btn { flex: 0 0 auto; margin-inline-start: auto; min-height: 36px; }
  .cal-tz { display: none; }
  .cal-modes { flex: 1 1 100%; }
  .cal-mode { flex: 1; padding: 7px 6px; min-height: 36px; font-size: 12.5px; }
  .cal-arrow, #cal-today { min-height: 36px; }
  /* Nothing inside may widen the page: a single long chat name in a block would
     otherwise reintroduce exactly the overflow the wrapping above removes. */
  .cal-wrap, .cal-main, .cal-body { min-width: 0; max-width: 100%; }
  .cal-body { overflow-x: hidden; }

  /* Agenda is the layout week borrows here, so it carries the phone sizing. */
  .cal-agenda { padding: 10px 12px 20px; gap: 14px; }
  .cal-ag-time { flex-basis: 40px; }
  .cal-agenda .cal-block { max-width: none; min-height: 44px; }
  .cal-grid-1 .cal-block { min-height: 44px; }
  .cal-b-time { display: inline; }

  /* Month stays a grid — seven small cells is what a month IS — but the cells
     need to be tappable and the pills need to fit. */
  .cal-month { padding: 10px 8px 20px; }
  .cal-month-head, .cal-month-grid { gap: 3px; }
  .cal-mday { min-height: 74px; padding: 5px; }
  .cal-pill { font-size: 10px; padding: 0 5px; }
  .cal-summary { padding: 10px 12px; gap: 8px; }
  .cal-sum-card { min-width: 78px; padding: 6px 10px; }
  .cal-legend { display: none; }
}

/* The drawer is a phone shape only. Above the breakpoint the panel is a column
   in the layout and the button that opens it means nothing. */
@media (min-width: 641px) {
  .cal-filter-btn, .cal-drawer-done, .cal-backdrop { display: none; }
}

/* ---- subscribe: the feed URL, and the fact that it is a credential ---- */
.cal-sub-btn { width: 100%; margin-block-start: 10px; }
.cal-sub-fresh {
  border: 1px solid var(--accent); border-radius: var(--radius);
  background: var(--accent-soft); padding: 10px 12px; margin-block-end: 14px;
}
/* The URL wraps rather than scrolls: somebody selecting it by hand (no
   clipboard permission, or a browser that refuses in an iframe) has to be able
   to see all of it, and a one-line box hides the half that matters. */
.cal-sub-url {
  font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px;
  word-break: break-all; user-select: all;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 6px; padding: 8px 10px;
}
.cal-sub-warn { margin-block-start: 8px; font-size: 12px; color: #92650a; }
.cal-sub-list { display: flex; flex-direction: column; gap: 6px; margin-block-end: 14px; }
.cal-sub-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  border: 1px solid var(--line); border-radius: var(--radius); padding: 8px 12px;
}
.cal-sub-name { font-weight: 600; font-size: 13px; }
.cal-sub-meta { font-size: 11.5px; color: var(--ink-soft); }
.cal-sub-empty { font-size: 13px; color: var(--ink-soft); }

/* ---- agenda: a fortnight with the empty hours taken out ----
 *
 * The grid keeps empty hours because "nothing between two and five" is
 * information when you are reading one day. Over fourteen days it is 300 blank
 * rows, so this mode drops them and keeps only the hours that happened. */
.cal-agenda { padding: 14px 20px 24px; display: flex; flex-direction: column; gap: 18px; }
.cal-ag-day { display: flex; flex-direction: column; gap: 8px; }
.cal-ag-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  margin: 0; padding-bottom: 6px; border-bottom: 1px solid var(--line);
  font-size: 14px; font-weight: 700;
}
.cal-ag-day.is-today .cal-ag-head { color: var(--accent); border-bottom-color: var(--accent); }
.cal-ag-count { font-size: 11.5px; font-weight: 600; color: var(--ink-soft); }
.cal-ag-hour { display: flex; gap: 12px; align-items: flex-start; }
.cal-ag-time {
  flex: 0 0 46px; padding-top: 4px; font-size: 11.5px; color: var(--ink-soft);
  font-variant-numeric: tabular-nums; text-align: end;
}
.cal-ag-blocks { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
/* Wider than a week column, so a preview can afford a second line here. */
.cal-agenda .cal-b-prev { white-space: normal; }
.cal-agenda .cal-block { max-width: 760px; }

/* ---- saved filters ---- */
.cal-preset { display: flex; flex-direction: column; gap: 5px; }
.cal-preset select {
  width: 100%; max-width: 100%; padding: 6px 8px; border-radius: 6px;
  border: 1px solid var(--line); background: var(--panel);
}
.cal-preset-actions { display: flex; gap: 10px; }

/* ---- the mail dropdown, reused in the calendar's side panel ----
 *
 * The component is the Mail screen's (.mdd); these rules only adapt it to a
 * 216px column instead of a full-width toolbar. Nothing here restyles the
 * popover itself, so a change to it still lands in both places. */
.cal-side .mdd { position: relative; width: 100%; }
.cal-side .mdd-trigger {
  width: 100%; display: flex; align-items: center; gap: 6px;
  justify-content: flex-start; text-align: start;
}
.cal-side .mdd-trigger-text {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Anchored to the trigger and allowed to be wider than the column it hangs
   from — a chat name does not fit in 216px and truncating the CHOICES defeats
   the search box above them. */
/* FIXED, not absolute: .cal-side is a scroll container (overflow-y:auto, which
   CSS computes to auto on both axes), so an absolutely positioned popover is
   clipped to the 180-216px column — exactly the width the search box exists to
   escape. Taken out of the flow here; the coordinates come from the trigger in
   bindCalDropdowns, which also flips it above when there is no room below. */
.cal-side .mdd-pop {
  position: fixed; z-index: 60;
  overflow: auto; overscroll-behavior: contain;
}
.cal-side .mdd-list { max-height: 260px; }
.cal-f .mdd-caret { margin-inline-start: auto; }

/* ---- desktop keeps the word, the phone shows the date ---- */
.cal-today-inline { display: none; }

@media (max-width: 640px) {
  /* THE HEADER IS TWO ROWS, NOT THREE.
     Title, nav and modes each took a line — about 158px of a 780px screen gone
     before any calendar. The date moves between the arrows and doubles as the
     Today button, which is what every mobile calendar does and what buys the
     row back. */
  /* ONE LINE: the date, then ‹ Today ›. The modes take the line below.
     display:contents on the wrapper lets the nav and the modes become direct
     flex items of the header, which is what allows the date to sit beside the
     nav while the modes still wrap onto their own row — they are in the same
     container in the markup, and splitting them any other way would mean two
     wrappers and a second set of gaps. */
  .cal-head-right { display: contents; }
  .cal-head-left { flex: 1 1 auto; min-width: 0; order: 0; }
  .cal-head-left h2 {
    font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .cal-nav { flex: 0 0 auto; order: 1; gap: 6px; }
  .cal-modes { flex: 1 1 100%; order: 2; }
  .cal-today-btn { min-height: 36px; }
  .cal-arrow { flex: 0 0 auto; min-width: 40px; min-height: 40px; }
  .cal-filter-btn { min-height: 40px; }
  .cal-mode { min-height: 40px; }

  /* MONTH: pills side by side rather than stacked.
     Four stacked pills on one busy day set the height of its whole ROW — the
     first week came out at 150px while the rest sat at 74. Wrapping them keeps
     every row close to the same height and the month on one screen. */
  .cal-pills { flex-direction: row; flex-wrap: wrap; gap: 3px; }
  .cal-mday { min-height: 62px; }
  .cal-mday-num { font-size: 12px; }
  .cal-dots { margin-top: 3px; }

  /* The day drill-down is a summary, not a section — it should not cost a third
     of the screen before the hours start. */
  /* The three counts share ONE row and the names take the next. They were on a
     single flex line with the name chips competing for it, which squeezed the
     cards below their own text and overlapped them — "1 Conve…" on top of "2
     People contac…". flex-basis:100% on the chips is what separates the rows. */
  .cal-summary { padding: 8px 12px; gap: 6px; }
  .cal-sum-card { flex: 1 1 0; min-width: 0; padding: 5px 8px; overflow: hidden; }
  .cal-sum-n { font-size: 16px; }
  .cal-sum-l { font-size: 10px; line-height: 1.25; }
  .cal-sum-people { flex: 1 1 100%; gap: 4px; max-height: 52px; overflow: hidden; }
  .cal-person { font-size: 11px; padding: 2px 7px; }

  /* An empty hour needs to be visible, not spacious — 44px each over 24 rows is
     a screen and a half of nothing between the interesting ones. */
  .cal-row { min-height: 34px; }
  .cal-gutter { font-size: 10.5px; padding: 3px 6px; }
}

/* The total is a phone-only shorthand; the desktop grid has the room for the
   four labelled pills and shows them instead. */
.cal-pill.total { display: none; }
@media (max-width: 640px) {
  .cal-pill.wa, .cal-pill.mail, .cal-pill.ticket, .cal-pill.sched { display: none; }
  .cal-pill.total {
    display: inline-block; background: var(--accent-soft); color: var(--accent);
    font-size: 11px; font-weight: 700; padding: 1px 7px;
  }
  .cal-pills { flex-direction: row; }
}

/* ---- the floating pair, and the day summary behind the "i" ----
 *
 * Above the phone breakpoint neither exists: the header has room for a filter
 * button and the summary strip has room to sit above the hours. */
.cal-fabs { display: none; }

.cal-info-counts {
  display: flex; gap: 10px; margin: 4px 0 14px;
}
.cal-info-counts div {
  flex: 1; text-align: center; padding: 8px 6px;
  background: var(--ground); border: 1px solid var(--line); border-radius: var(--radius);
}
.cal-info-counts b { display: block; font-size: 19px; line-height: 1.1; }
.cal-info-counts span { font-size: 11px; color: var(--ink-soft); }
.cal-info-sec { margin-block-end: 14px; }
.cal-info-sec h4 {
  margin: 0 0 6px; font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-soft);
}
.cal-info-sec ul { list-style: none; margin: 0; padding: 0; }
.cal-info-sec li {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 7px 0; border-top: 1px solid var(--line); font-size: 13.5px;
}
.cal-info-sec li span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-info-sec li em { flex: 0 0 auto; font-style: normal; font-size: 12px; color: var(--ink-soft); }

@media (max-width: 640px) {
  /* The header keeps the date, the arrows and the modes; the control that opens
     a panel floats instead, which is what makes room for the other three. */
  .cal-filter-btn { display: none; }
  /* The summary moved behind the "i" — inline it cost about a fifth of the
     screen before a single message was visible. */
  .cal-summary { display: none; }

  .cal-fabs {
    display: flex; flex-direction: column; gap: 10px;
    position: fixed; z-index: 42;
    inset-inline-end: 14px;
    /* Clear of the bottom rail, which is 58px on a phone. */
    bottom: 72px;
  }
  .cal-fab {
    width: 48px; height: 48px; border-radius: 50%;
    display: grid; place-items: center; position: relative;
    border: 1px solid var(--line); background: var(--panel);
    box-shadow: 0 3px 12px rgba(20, 28, 25, .22);
    font-size: 18px; color: var(--ink-soft);
  }
  .cal-fab-info { font-style: italic; font-weight: 700; font-family: Georgia, serif; }
  .cal-fab-filter.on { background: var(--accent); border-color: var(--accent); color: #fff; }
  .cal-fab-glyph { transform: rotate(90deg); font-size: 20px; }
  .cal-fab-n {
    position: absolute; top: -2px; inset-inline-end: -2px;
    min-width: 19px; height: 19px; border-radius: 10px; padding: 0 5px;
    display: grid; place-items: center;
    background: var(--danger); color: #fff; font-size: 11px; font-weight: 700;
    border: 2px solid var(--panel);
  }
}

/* ---- the day-summary dialog's sticky title bar ----
 *
 * .modal is the scrolling element, so a close control placed in the normal flow
 * scrolls out of reach — and this is the one calendar dialog that genuinely
 * runs long. The negative margins pull the bar out to the modal's own padding
 * (20px 22px) so its background covers the full width as content passes under. */
.cal-info-head {
  position: sticky; top: -20px; z-index: 2;
  display: flex; align-items: flex-start; gap: 10px;
  margin: -20px -22px 12px; padding: 20px 22px 10px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}
.cal-info-head h3 { flex: 1; min-width: 0; margin: 0; }
.cal-info-x {
  flex: 0 0 auto; width: 32px; height: 32px; margin: -4px -6px 0 0;
  border: none; background: none; border-radius: 8px;
  font-size: 15px; color: var(--ink-soft); display: grid; place-items: center;
}
.cal-info-x:hover { background: var(--ground); color: var(--ink); }

/* The mail pager: a fixed strip under the list, outside the scrolling area so
   it does not move while the rows do. */
.mail-pager { display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  padding: 6px 10px; border-top: 1px solid var(--line); flex: 0 0 auto; }
.mail-pager-range { font-size: 12px; opacity: .7; margin-inline-end: auto; }
.mail-pager .btn { min-width: 30px; padding: 2px 8px; font-size: 15px; line-height: 1.4; }
.mail-pager .btn[disabled] { opacity: .35; cursor: default; }

/* ---- phone calls (calls.js) ---- */
.call-bar {
  position: fixed; z-index: 1200; inset-inline-end: 16px; bottom: 16px;
  width: min(360px, calc(100vw - 32px));
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--line); border-radius: 14px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, .2);
  padding: 12px 14px; display: grid; gap: 10px;
}
.call-bar-main { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-width: 0; }
.call-bar-who { display: flex; align-items: center; gap: 10px; min-width: 0; }
.call-bar-id { display: grid; min-width: 0; }
.call-bar-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.call-bar-sub { color: var(--ink-soft); font-size: 12px; font-variant-numeric: tabular-nums; direction: ltr; text-align: start; }
.call-bar-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--warn); flex: 0 0 9px; animation: call-pulse 1.4s infinite; }
.call-bar[data-phase="live"] .call-bar-dot { background: var(--accent); }
.call-bar[data-phase="reconnecting"] .call-bar-dot { background: var(--danger); }
.call-bar[data-phase="ended"] .call-bar-dot, .call-bar[data-phase="wrapup"] .call-bar-dot { background: var(--ink-soft); animation: none; }
.call-bar-status { color: var(--ink-soft); font-variant-numeric: tabular-nums; white-space: nowrap; font-size: 13px; }
.call-bar[data-phase="live"] .call-bar-status { color: var(--accent); font-weight: 600; }
.call-bar-actions { display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }
.call-bar-actions .btn.on { background: var(--warn-soft); }
.btn.call-hangup { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn.call-hangup:hover { filter: brightness(.95); background: var(--danger); }
.call-keys { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.call-keys.hidden { display: none; }
.call-wrap { display: grid; gap: 8px; }
.call-wrap-label { font-size: 13px; color: var(--ink-soft); }
.call-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.call-chip { border: 1px solid var(--line); background: var(--panel); color: var(--ink); border-radius: 999px; padding: 3px 10px; font-size: 12px; cursor: pointer; }
.call-chip.on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.call-chip.static { cursor: default; }
.call-note { width: 100%; box-sizing: border-box; resize: vertical; border: 1px solid var(--line); border-radius: 8px; padding: 6px 8px; font: inherit; background: var(--panel); color: var(--ink); }
@keyframes call-pulse { 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .call-bar-dot { animation: none; } }

.call-contact-btn { display: inline-flex; align-items: center; gap: 6px; margin-inline-start: auto; }
.call-contact-btn svg { width: 15px; height: 15px; }

/* a call, as a card in the conversation */
.call-msg-row { justify-content: center; }
.call-msg-row .msg { max-width: min(420px, 90%); }
.call-card { background: var(--info-soft); border: 1px solid var(--line); border-radius: 12px; padding: 8px 12px; display: grid; gap: 4px; font-size: 13px; }
.call-card-ic { margin-inline-end: 4px; }
.call-card-meta { color: var(--ink-soft); font-size: 12px; }
.call-card-note { white-space: pre-wrap; }
.call-card-actions { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.call-card-actions:empty { display: none; }
.call-rec-audio { width: 100%; max-width: 320px; height: 34px; }

/* the Calls view */
.calls-view { flex: 1 1 auto; width: 100%; min-width: 0; display: grid; grid-template-columns: 300px minmax(0, 1fr); align-content: start; gap: 16px; padding: 16px; height: 100%; box-sizing: border-box; overflow: auto; background: var(--ground); }
.calls-view h2 { font-size: 15px; margin: 0 0 10px; }
.calls-dialer, .calls-history { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 16px; min-width: 0; }
.calls-dialer { align-self: start; display: grid; gap: 10px; }
.dial-input { width: 100%; box-sizing: border-box; font-size: 20px; text-align: center; padding: 10px; border: 1px solid var(--line); border-radius: 10px; direction: ltr; font-variant-numeric: tabular-nums; background: var(--panel); color: var(--ink); }
.dial-pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.dial-key { height: 48px; border-radius: 10px; border: 1px solid var(--line); background: var(--ground); color: var(--ink); font-size: 18px; cursor: pointer; }
.dial-key:hover { background: var(--accent-soft); }
.dial-call { display: inline-flex; align-items: center; justify-content: center; gap: 8px; height: 44px; font-size: 15px; }
.calls-hint { margin: 0; font-size: 12px; color: var(--ink-soft); }
.calls-history-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.calls-history-head h2 { margin: 0; }
.calls-filters { display: flex; gap: 8px; }
.calls-list { display: grid; }
.calls-empty { padding: 24px 0; text-align: center; }
.call-row { display: flex; gap: 12px; padding: 12px 4px; border-top: 1px solid var(--line); min-width: 0; }
.call-row:first-child { border-top: none; }
.call-row-ic { width: 34px; height: 34px; flex: 0 0 34px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: var(--ground); font-size: 16px; }
.call-row-ic.st-answered { background: var(--accent-soft); }
.call-row-ic.st-busy, .call-row-ic.st-no_answer, .call-row-ic.st-failed { background: #fdeaea; }
.call-row-main { flex: 1; min-width: 0; display: grid; gap: 4px; }
.call-row-top { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.call-row-name { font-weight: 600; }
.call-row-num { color: var(--ink-soft); font-size: 12px; direction: ltr; }
.call-row-meta { color: var(--ink-soft); font-size: 12px; }
.call-row-wrap { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.call-row-note { font-size: 13px; white-space: pre-wrap; }
.call-row-actions { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.call-edit { display: grid; gap: 8px; margin-top: 4px; }
.calls-more { justify-self: center; margin-top: 10px; }
@media (max-width: 640px) {
  /* Not on the phone's bottom bar (it starts at 640px): that bar holds nine at
     most (test/briefing.mjs), and placing calls from a phone browser is not the
     workflow — mobile calling is its own project (CALLING.md). Chat and contact
     Call buttons still work. */
  .rail-calls { display: none !important; }
}
@media (max-width: 760px) {
  .calls-view { grid-template-columns: 1fr; padding: 10px; }
  .call-bar { inset-inline-end: 8px; bottom: 66px; width: calc(100vw - 16px); }
}

/* Label filter chips over Settings → Numbers. */
.phone-label-filter { margin: 4px 0 12px; }
.phone-label-chip { border: 0; cursor: pointer; opacity: .55; }
.phone-label-chip:not([style]) { background: var(--line); color: var(--ink); }
.phone-label-chip.active { opacity: 1; outline: 2px solid var(--ink-soft); outline-offset: 1px; }
.phone-name .mini-label { margin-inline-start: 6px; vertical-align: 2px; }

/* On WhatsApp? chips on contact rows. */
.ctag.wa-on { background: #e3f3e3; color: #0a5d0a; }
.ctag.wa-off { background: #fbe6e3; color: #9b2c1f; }
.ctag.wa-chk { background: var(--line); color: var(--ink-soft); }

/* Deleted on WhatsApp, kept here: the text struck through, with a tag. */
.bubble-text.msg-deleted { text-decoration: line-through; opacity: .6; }
.deleted-tag { display: block; font-size: 11px; color: var(--ink-soft); margin-top: 2px; }

/* Which number a message used, in a multi-number chat. */
.msg-via { font-weight: 600; color: var(--ink-soft); }

/* Number colour: a dot on the avatar and the number after the name. */
.avatar { position: relative; }
.avatar-num { position: absolute; inset-inline-end: -2px; bottom: -2px; width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--panel, #fff); }
.chat-num { font-weight: 600; white-space: nowrap; }

/* The phone's More sheet (openRailMore): every page not on the bar, with a
   line saying what it is for. */
.rail-sheet { display: flex; flex-direction: column; gap: 2px; margin: 4px 0 2px; }
.rail-sheet-row { display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px 8px; border: none;
  background: none; border-radius: 10px; text-align: start; font: inherit; color: var(--ink); cursor: pointer; }
.rail-sheet-row:hover, .rail-sheet-row.active { background: var(--accent-soft); }
.rail-sheet-ico { width: 36px; height: 36px; flex: none; border-radius: 10px; background: var(--accent-soft);
  color: var(--accent); display: grid; place-items: center; }
.rail-sheet-txt { min-width: 0; }
.rail-sheet-txt b { display: block; font-size: 14px; }
.rail-sheet-txt small { display: block; font-size: 12.5px; color: var(--ink-soft); line-height: 1.35; }

/* ---------- UX plan, Phase 2 ---------- */

/* Settings, grouped: section headings, a line under each item, and a search. */
.settings-find { width: 100%; height: 34px; padding: 0 10px; margin: 0 0 6px; border: 1px solid var(--line);
  border-radius: 9px; background: var(--ground); color: var(--ink); font: inherit; font-size: 13px; }
.settings-find:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
.settings-group-h { font-size: 10.5px; font-weight: 750; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-soft); opacity: .8; margin: 12px 8px 4px; }
.settings-tab .st-name { display: block; font-weight: 600; }
.settings-tab .st-desc { display: block; font-size: 11.5px; line-height: 1.3; color: var(--ink-soft); font-weight: 400; margin-top: 1px; }
.settings-tab.active .st-desc { color: var(--accent); opacity: .85; }
.settings-nomatch { font-size: 12.5px; color: var(--ink-soft); margin: 10px 8px; }

/* Ctrl+K: the jump bar. */
.cmdk-btn { display: inline-flex; align-items: center; gap: 8px; height: 34px; min-width: 240px; max-width: 380px; flex: 0 1 380px;
  margin-inline: 18px auto; padding: 0 8px 0 10px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--ground); color: var(--ink-soft); font: inherit; font-size: 13px; cursor: pointer; }
.cmdk-btn:hover { border-color: var(--accent); }
.cmdk-btn .rail-ico { width: 16px; height: 16px; }
.cmdk-btn kbd { margin-inline-start: auto; font: 11px ui-monospace, Menlo, monospace; padding: 1px 5px;
  border: 1px solid var(--line); border-radius: 5px; background: var(--panel); color: var(--ink-soft); }
.cmdk-scrim { position: fixed; inset: 0; z-index: 900; background: rgba(8, 16, 12, .42);
  display: flex; align-items: flex-start; justify-content: center; padding: 12vh 16px 16px; }
.cmdk { width: 600px; max-width: 100%; background: var(--panel); border: 1px solid var(--line); border-radius: 14px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, .25); overflow: hidden; display: flex; flex-direction: column; max-height: 70vh; }
.cmdk input { height: 52px; border: 0; border-bottom: 1px solid var(--line); padding: 0 16px; font: inherit; font-size: 15.5px;
  background: var(--panel); color: var(--ink); outline: none; }
.cmdk-list { overflow-y: auto; padding: 6px; }
.cmdk-h { font-size: 10.5px; font-weight: 750; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-soft); padding: 8px 10px 4px; }
.cmdk-row { display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px; border: 0; border-radius: 9px;
  background: none; text-align: start; font: inherit; color: var(--ink); cursor: pointer; }
.cmdk-row.on, .cmdk-row:hover { background: var(--accent-soft); }
.cmdk-ico { width: 20px; flex: none; display: grid; place-items: center; color: var(--accent); }
.cmdk-ico .rail-ico { width: 18px; height: 18px; }
.cmdk-txt { min-width: 0; }
.cmdk-txt b { display: block; font-weight: 600; font-size: 14px; }
.cmdk-txt small { display: block; font-size: 12px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cmdk-none { padding: 18px; color: var(--ink-soft); font-size: 13.5px; }
.cmdk-foot { padding: 7px 14px; border-top: 1px solid var(--line); font-size: 11.5px; color: var(--ink-soft); }

/* The wide bar: the name beside each icon. */
.rail-toggle { display: flex; flex-direction: column; align-items: center; gap: 3px; width: 100%; padding: 7px 0 6px;
  border: none; background: none; border-radius: 10px; color: var(--ink-soft); font: inherit; cursor: pointer; flex: none; margin-bottom: 2px; }
.rail-toggle:hover { background: var(--ground); color: var(--ink); }
body.rail-wide #rail { flex-basis: 196px; padding: 10px 8px; }
body.rail-wide .rail-btn, body.rail-wide .rail-setup, body.rail-wide .rail-toggle {
  flex-direction: row; justify-content: flex-start; gap: 11px; padding: 8px 10px; }
body.rail-wide .rail-lbl { font-size: 13.5px; text-align: start; -webkit-line-clamp: 1; letter-spacing: 0; }
body.rail-wide .rail-sep { margin: 6px 10px; }
body.rail-wide .rail-count { position: static; margin-inline-start: auto; }
body.rail-wide .rail-toggle svg { transform: scaleX(-1); }
[dir="rtl"] body.rail-wide .rail-toggle svg, body.rail-wide[dir="rtl"] .rail-toggle svg { transform: none; }

@media (max-width: 640px) {
  .cmdk-btn { min-width: 0; flex: none; margin-inline: 8px auto; padding: 0 8px; }
  .cmdk-btn span, .cmdk-btn kbd { display: none; }
  .rail-toggle { display: none; }
  body.rail-wide #rail { flex-basis: 60px; padding: 0 4px; }
  body.rail-wide .rail-btn { flex-direction: column; gap: 3px; padding: 8px 0 6px; justify-content: center; }
  body.rail-wide .rail-lbl { font-size: 10.5px; text-align: center; }
  /* Settings on a phone stays a scrolling strip of names. */
  .settings-find, .settings-group-h, .settings-tab .st-desc, .settings-nomatch { display: none; }
  .settings-group { display: contents; }
  .cmdk-scrim { padding-top: 8vh; }
}

/* ---------- Dark mode (UX plan, Phase 3) ----------
   html[data-theme="dark"] is set before the page paints (inline script in
   index.html) from Settings › Appearance: Light (default), Dark, or System.
   Most of the app is drawn from the variables, so the theme is mostly here;
   the rules below it repaint the colours that were written in directly. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --ink: #e4ebe7;
  --ink-soft: #9fb0a9;
  --tick-grey: #8696a0;
  --tick-read: #53bdeb;
  --ground: #0f1513;
  --panel: #161e1b;
  --line: #27322e;
  --accent: #2ba878;
  --accent-soft: #143428;
  --info-soft: #172440;
  --warn-soft: #382a14;
  --warn: #f0a54a;
  --danger: #f07167;
  --info: #7aa2ff;
  --note: #3a3418;
}
:root[data-theme="dark"] body { background: var(--ground); }
:root[data-theme="dark"] input, :root[data-theme="dark"] select, :root[data-theme="dark"] textarea {
  background-color: var(--ground); border-color: var(--line); color: var(--ink); }
:root[data-theme="dark"] ::placeholder { color: #6f807a; }
/* Chat bubbles: ours green, theirs panel, notes amber, automation blue-grey. */
:root[data-theme="dark"] .msg.out .bubble { background: #163a2c; border-color: #1f4d3a; }
:root[data-theme="dark"] .msg .bubble.auto { background: #1b2236; border-color: #263052; }
:root[data-theme="dark"] .msg .bubble.note { background: #33301a; border-color: #4a4422; }
:root[data-theme="dark"] .msg-link { color: #6cc4ff; }
:root[data-theme="dark"] .msg.out .msg-link { color: #7fe0b5; }
:root[data-theme="dark"] .msg.out .ccard-actions,
:root[data-theme="dark"] .msg.out .ccard-actions > * + * { border-color: #1f4d3a; }
:root[data-theme="dark"] .msg.out .ccard-stack .ccard-av { border-color: #163a2c; }
:root[data-theme="dark"] .quoted { background: rgba(60, 196, 143, .08); }
/* Shadows read as dirt on a dark ground. */
:root[data-theme="dark"] .modal, :root[data-theme="dark"] .ctx-menu, :root[data-theme="dark"] .notif-panel { box-shadow: 0 12px 40px rgba(0, 0, 0, .55); }
:root[data-theme="dark"] .rail-hover { background: #e4ebe7; color: #0f1513; }

/* Settings › Appearance: three cards with a miniature of each look. */
.appear-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 200px)); gap: 14px; margin-top: 16px; }
@media (max-width: 640px) { .appear-grid { grid-template-columns: 1fr; } }
.appear-opt { display: flex; flex-direction: column; gap: 4px; padding: 12px; border: 2px solid var(--line); border-radius: 12px;
  background: var(--panel); cursor: pointer; }
.appear-opt.on { border-color: var(--accent); }
.appear-opt input { position: absolute; opacity: 0; pointer-events: none; }
.appear-opt b { font-size: 14px; margin-top: 6px; }
.appear-opt small { font-size: 12px; color: var(--ink-soft); }
.appear-swatch { display: grid; grid-template-columns: 18px 1fr; grid-template-rows: 1fr 1fr; gap: 4px; height: 74px;
  padding: 6px; border-radius: 8px; border: 1px solid var(--line); }
.appear-swatch i { border-radius: 4px; }
.appear-swatch i:first-child { grid-row: span 2; }
.appear-light { background: #f5f7f6; } .appear-light i { background: #ffffff; } .appear-light i:first-child { background: #e4f2ec; }
.appear-dark { background: #0f1513; } .appear-dark i { background: #161e1b; } .appear-dark i:first-child { background: #143428; }
.appear-system { background: linear-gradient(135deg, #f5f7f6 50%, #0f1513 50%); }
.appear-system i { background: rgba(127, 127, 127, .35); } .appear-system i:first-child { background: rgba(15, 122, 84, .45); }

/* generated: dark overrides for hard-coded light colours */
:root[data-theme="dark"] .plan-badge.starter { background: #182540; border-color: #2a3d68; }
:root[data-theme="dark"] .plan-badge.pro { border-color: #1f4d3a; }
:root[data-theme="dark"] .plan-badge.trial { background: #372a14; border-color: #5a4422; }
:root[data-theme="dark"] .lsug:hover { border-color: #1f4d3a; }
:root[data-theme="dark"] .jump-mention .jump-count { background: var(--panel); }
:root[data-theme="dark"] .msg.out .bubble { background: #163a2c; border-color: #1f4d3a; }
:root[data-theme="dark"] .msg .bubble.auto { background: #182540; border-color: #2a3d68; }
:root[data-theme="dark"] .msg .bubble.note { border-color: #5a4422; }
:root[data-theme="dark"] .msg.out .ccard-actions,
:root[data-theme="dark"] .msg.out .ccard-actions > * + * { border-color: #1f4d3a; }
:root[data-theme="dark"] .msg.out .ccard-stack .ccard-av { border-color: #1f4d3a; }
:root[data-theme="dark"] .note-toggle.on { border-color: #5a4422; }
:root[data-theme="dark"] .cs-item-body mark { background: #372a14; }
:root[data-theme="dark"] .cp-req { background: #372a14; }
:root[data-theme="dark"] .det-wait { background: #372a14; border-color: #5a4422; color: #f0c27a; }
:root[data-theme="dark"] .prio.low { background: #212b27; }
:root[data-theme="dark"] .prio.medium { background: #182540; }
:root[data-theme="dark"] .prio.high { background: #372a14; }
:root[data-theme="dark"] .prio.urgent { background: #3a1d1d; }
:root[data-theme="dark"] .tp-msg.out { background: #163a2c; border-color: #1f4d3a; }
:root[data-theme="dark"] .tp-msg.note { background: #372a14; border-color: #5a4422; }
:root[data-theme="dark"] .rule-kind { background: #182540; }
:root[data-theme="dark"] .slider { background: #212b27; }
:root[data-theme="dark"] .dot.offline { background: #212b27; }
:root[data-theme="dark"] .pstatus.warn { background: #372a14; }
:root[data-theme="dark"] .pstatus.err { background: #3a1d1d; }
:root[data-theme="dark"] .role-tag.admin { background: #182540; }
:root[data-theme="dark"] .role-tag.readonly { background: #372a14; }
:root[data-theme="dark"] .lb-warn { background: #372a14; border-color: #5a4422; color: #f0c27a; }
:root[data-theme="dark"] .ctx-reacts { border-bottom-color: #34413c; }
:root[data-theme="dark"] .btn.danger { border-color: #5c2c2c; }
:root[data-theme="dark"] .btn.danger:hover { background: #3a1d1d; }
:root[data-theme="dark"] .sw-top i:nth-child(2) { background: #372a14; }
:root[data-theme="dark"] .sw-top i:nth-child(3) { background: #163a2c; }
:root[data-theme="dark"] .sw-msg.out { background: #163a2c; border-color: #1f4d3a; }
:root[data-theme="dark"] .auth-form input:hover { border-color: #34413c; }
:root[data-theme="dark"] .auth-error { background: #3a1d1d; border-color: #5c2c2c; }
:root[data-theme="dark"] .auth-note { border-color: #1f4d3a; }
:root[data-theme="dark"] .sync-badge.queued { background: #212b27; }
:root[data-theme="dark"] .sync-badge.failed { background: #3a1d1d; }
:root[data-theme="dark"] .media-note.failed { background: #372a14; border-color: #5a4422; }
:root[data-theme="dark"] .docview-acts .btn { background: var(--panel); }
:root[data-theme="dark"] .lightbox-bar .btn { background: var(--panel); }
:root[data-theme="dark"] .mchip.warn { background: #372a14; }
:root[data-theme="dark"] .mchip.muted { background: #182540; color: #9db9ff; }
:root[data-theme="dark"] .mail-chip-gmail { background: #3a1d1d; border-color: #5c2c2c; color: #f4a7a2; }
:root[data-theme="dark"] .mail-chip-zoho { background: #372a14; border-color: #5a4422; color: #f0c27a; }
:root[data-theme="dark"] .mail-msg.out { border-color: #1f4d3a; }
:root[data-theme="dark"] .mail-msg.out .mail-msg-head { background: #163a2c; }
:root[data-theme="dark"] .mail-msg.out .mail-msg-head:hover { background: #163a2c; }
:root[data-theme="dark"] .mail-compose-warn { background: #372a14; border-color: #5a4422; color: #f0c27a; }
:root[data-theme="dark"] .mail-msg-collapsed:hover { border-color: #34413c; }
:root[data-theme="dark"] .mail-att-pdf .mail-att-ico { background: #3a1d1d; }
:root[data-theme="dark"] .mail-att-doc .mail-att-ico { background: #182540; }
:root[data-theme="dark"] .mail-att-img .mail-att-ico { background: #272040; color: #c4b2ff; }
:root[data-theme="dark"] .mail-att-zip .mail-att-ico { background: #372a14; }
:root[data-theme="dark"] .csrc-wa { background: #163a2c; border-color: #1f4d3a; color: #7fdcb0; }
:root[data-theme="dark"] .csrc-mail { background: #3a1d1d; border-color: #5c2c2c; color: #f4a7a2; }
:root[data-theme="dark"] .mcp-tag.send { border-color: #5a4422; background: #372a14; color: #f0c27a; }
:root[data-theme="dark"] .phone-banner-inner { background: #3a1d1d; border-bottom-color: #5c2c2c; color: #f4a7a2; }
:root[data-theme="dark"] .tag-chip { background: #372a14; }
:root[data-theme="dark"] .tag-row.is-suggested { background: #372a14; }
:root[data-theme="dark"] .privacy-banner-inner { background: #372a14; border-bottom-color: #5a4422; color: #f0c27a; }
:root[data-theme="dark"] .tag-warn { background: #3a1d1d; color: #f4a7a2; }
:root[data-theme="dark"] .bill-pill-active { background: #163a2c; color: #7fdcb0; }
:root[data-theme="dark"] .bill-pill-cancelling { background: #372a14; color: #f0c27a; }
:root[data-theme="dark"] .bill-pill-trial { background: #182540; color: #9db9ff; }
:root[data-theme="dark"] .bill-pill-failing { background: #3a1d1d; color: #f4a7a2; }
:root[data-theme="dark"] #wa-2fa-banner .tfb-dot { background: #3a1d1d; }
:root[data-theme="dark"] .tag-filter { border-color: #34413c; background: var(--panel); }
:root[data-theme="dark"] .phone-banner-inner.pb-hold { background: #372a14; border-bottom-color: #5a4422; color: #f0c27a; }
:root[data-theme="dark"] .tri-interrupt { background: #3a1d1d; }
:root[data-theme="dark"] .lb-rule.off { background: #372a14; border-color: #5a4422; color: #f0c27a; }
:root[data-theme="dark"] .lb-rule.on { background: #163a2c; border-color: #1f4d3a; }
:root[data-theme="dark"] .wh-src { background: #372a14; border-color: #5a4422; color: #f0c27a; }
:root[data-theme="dark"] .wh-err { background: #3a1d1d; border-color: #5c2c2c; color: #f4a7a2; }
:root[data-theme="dark"] .cal-swatch.in { border-color: #1f4d3a; }
:root[data-theme="dark"] .cal-trunc { background: #372a14; border-color: #5a4422; }
:root[data-theme="dark"] .cal-block.in:hover { background: #163a2c; }
:root[data-theme="dark"] .cal-block.mail.in:hover { background: #182540; }
:root[data-theme="dark"] .cal-block.out:not(.sched) .cal-b-unread { background: var(--panel); }
:root[data-theme="dark"] .cal-block.note { border-block-color: #5a4422; border-inline-end-color: #5a4422; }
:root[data-theme="dark"] .cal-block.note:hover { background: #372a14; }
:root[data-theme="dark"] .cal-block.ticket:hover { background: #372a14; }
:root[data-theme="dark"] .cal-block.ticket.st-closed:hover { background: #212b27; }
:root[data-theme="dark"] .cal-pill.mail { background: #182540; }
:root[data-theme="dark"] .cal-pill.ticket { background: #372a14; color: #f0c27a; }
:root[data-theme="dark"] .cal-pill.sched { background: #182540; color: #9db9ff; }
:root[data-theme="dark"] .call-row-ic.st-busy,
:root[data-theme="dark"] .call-row-ic.st-no_answer,
:root[data-theme="dark"] .call-row-ic.st-failed { background: #3a1d1d; }
:root[data-theme="dark"] .ctag.wa-on { background: #163a2c; color: #7fdcb0; }
:root[data-theme="dark"] .ctag.wa-off { background: #3a1d1d; color: #f4a7a2; }

/* Each settings item on its own line, however short its name. */
.settings-group .settings-tab { display: block; width: 100%; }
/* A private note stays amber in dark mode, also when it sits on our side. */
:root[data-theme="dark"] .msg .bubble.note,
:root[data-theme="dark"] .msg.out .bubble.note { background: #33301a; border-color: #4a4422; }

/* Empty states that explain the page (emptyStateHtml). */
.es { max-width: 460px; margin: 48px auto; text-align: center; color: var(--ink); }
.es-art { width: 64px; height: 64px; margin: 0 auto 14px; border-radius: 18px; background: var(--accent-soft); color: var(--accent);
  display: grid; place-items: center; }
.es-art .rail-ico { width: 30px; height: 30px; }
.es h3 { margin: 0 0 6px; font-size: 18px; }
.es p { margin: 0 0 16px; color: var(--ink-soft); line-height: 1.5; }
.es-steps { list-style: none; padding: 0; margin: 0 0 18px; display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; counter-reset: es; }
.es-steps li { counter-increment: es; flex: 1 1 120px; max-width: 150px; padding: 9px 10px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--panel); text-align: start; font-size: 12.5px; color: var(--ink-soft); }
.es-steps li b { display: block; color: var(--ink); font-size: 12.5px; }
.es-steps li b::before { content: counter(es) ". "; color: var(--accent); }
.mail-connect-why { max-width: 320px; margin: 2px auto 10px; color: var(--ink-soft); font-size: 13px; line-height: 1.45; }

/* "What is this page?" — once per page (renderPageIntro). Floats over the top
   of the page so it moves nothing; Got it puts it away for good. */
.page-intro { position: absolute; top: 12px; inset-inline: 0; margin: 0 auto; width: min(620px, calc(100% - 24px)); z-index: 30;
  display: flex; align-items: center; gap: 12px; padding: 11px 12px 11px 14px; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--line); border-inline-start: 4px solid var(--info);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .12); }
.page-intro-ico { color: var(--info); flex: none; display: grid; }
.page-intro-txt { flex: 1; min-width: 0; font-size: 13px; color: var(--ink-soft); line-height: 1.4; }
.page-intro-txt b { display: block; color: var(--ink); font-size: 13.5px; }
@media (max-width: 640px) { .page-intro { top: 8px; } }

/* ---------- Polish (UX plan, Phase 3): small, quick motion ----------
   Things that appear fade and rise a few pixels; buttons answer the hover.
   Off for anyone whose system asks for reduced motion. */
@keyframes md-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes md-fade { from { opacity: 0; } to { opacity: 1; } }
.page-intro, .cmdk, .es, .rail-sheet, .appear-grid { animation: md-rise .18s ease-out both; }
.cmdk-scrim { animation: md-fade .12s ease-out both; }
.rail-btn, .rail-more, .rail-setup, .rail-toggle, .settings-tab, .cmdk-row, .appear-opt, .btn {
  transition: background-color .12s ease, color .12s ease, border-color .12s ease, box-shadow .12s ease; }
body.rail-wide #rail, #rail { transition: flex-basis .18s ease; }
.appear-opt:hover { border-color: var(--accent); }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
}

/* Loading placeholders (skeletonHtml): shapes where the content will be. */
@keyframes md-shimmer { 0% { background-position: 100% 0; } 100% { background-position: -100% 0; } }
.skel { display: block; border-radius: 6px; background: linear-gradient(90deg, var(--line) 25%, var(--ground) 50%, var(--line) 75%);
  background-size: 200% 100%; animation: md-shimmer 1.4s ease-in-out infinite; }
.skel-line { height: 10px; margin: 7px 0; }
.skel-card { padding: 14px 16px; }
.skel-rows { padding: 6px 0; }
.skel-row { display: flex; align-items: center; gap: 12px; padding: 10px 16px; }
.skel-av { width: 36px; height: 36px; border-radius: 50%; flex: none; }
.skel-txt { flex: 1; }
.skel-chat { display: flex; flex-direction: column; gap: 12px; padding: 24px 18px; }
.skel-bubble { height: 38px; border-radius: 12px; }
.skel-bubble.out { align-self: flex-end; }
.skel-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 14px 0; }
@media (max-width: 640px) { .skel-stats { grid-template-columns: 1fr 1fr; } }
.skel-stat { padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); }
.skel-detail { padding: 18px; display: flex; flex-direction: column; gap: 12px; }
.skel-cal { padding: 12px; }
@media (prefers-reduced-motion: reduce) { .skel { animation: none; } }
.cal-empty-hint { margin-top: 6px; font-size: 12.5px; color: var(--ink-soft); opacity: .85; }

/* ---------- Consistent header & sidebars ----------
   The top bar, the left bar (#rail) and the second columns beside it
   (.settings-nav, #chatlist, .mail-list-pane, .contacts-list-pane, .cal-side)
   share one grid: the same 12px gutter, the same 12px top inset, a 32px title
   row (36px), 34px search fields with a 10px corner, 10px-radius items with the same
   hover/active wash, and one small-caps heading style. Desktop only — the
   phone layout (bottom bar, settings strip, off-canvas filters) is untouched. */
:root { --col-gutter: 12px; --col-top: 12px; --col-title-h: 36px; --page-x: 24px; --bar-h: 56px; --item-radius: 10px; --field-h: 34px; --field-radius: 10px; }

@media (min-width: 641px) {
  /* Header: the search starts one gutter after the brand; in the wide bar the
     brand fills the bar's width, so the search lines up with the column below. */
  #topbar { padding-inline: 16px; }
  .cmdk-btn { height: var(--field-h); border-radius: var(--field-radius); margin-inline-start: var(--col-gutter); }
  body.rail-wide #topbar .brand { min-inline-size: calc(196px - 16px); }

  /* The left bar: same top inset as the columns; wide bar uses the column gutter. */
  #rail { padding-block-start: var(--col-top); }
  body.rail-wide #rail { padding: var(--col-top) var(--col-gutter) 10px; }
  .rail-sep, body.rail-wide .rail-sep { margin: 6px 10px; }
  .rail-btn, .rail-more, .rail-setup, .rail-toggle { border-radius: var(--item-radius); }

  /* Columns: one surface, one hairline, one gutter, one top inset. */
  .settings-nav, #chatlist, .mail-list-pane, .contacts-list-pane, .cal-side { background: var(--panel); }
  .settings-nav { padding: var(--col-top) var(--col-gutter); gap: 2px; }
  .settings-nav > * { flex-shrink: 0; }            /* the search box was squashed to 19px */
  .cal-side { padding: var(--col-top) var(--col-gutter); }
  .chatlist-head { padding: var(--col-top) var(--col-gutter) 8px; }
  .mail-head, .contacts-head { padding: var(--col-top) var(--col-gutter) 8px; align-items: center; }
  .mail-search, .contacts-search { padding-inline: var(--col-gutter); }

  /* Column titles: one size, one weight, one 32px row so the first lines meet. */
  .settings-nav h3, .chatlist-title, .mail-head h2, .contacts-head h2, .cal-side-head {
    font-size: 16px; font-weight: 700; letter-spacing: -0.01em; text-transform: none; color: var(--ink); line-height: 1.2; }
  .settings-nav h3 { display: flex; align-items: center; min-height: var(--col-title-h); margin: 0 0 6px; }
  .chatlist-top, .mail-head, .contacts-head { min-height: calc(var(--col-title-h) + var(--col-top) + 8px); }
  .chatlist-top { min-height: var(--col-title-h); }
  .cal-side-head { min-height: var(--col-title-h); align-items: center; }
  .cal-side-head .cal-clear { font-size: 12px; font-weight: 600; }

  /* Search fields in the columns match the header's search. */
  .settings-find, #chatlist .search-input, .mail-search .search-input, .contacts-search .search-input {
    height: var(--field-h); border-radius: var(--field-radius); padding: 0 10px; font-size: 13px;
    border: 1px solid var(--line); background: var(--ground); color: var(--ink); }
  .settings-find { margin: 0; }

  /* Items: the rail's shape and wash. */
  .settings-tab { border-radius: var(--item-radius); padding: 8px 10px; color: var(--ink-soft); }
  .settings-tab:hover { background: var(--ground); color: var(--ink); }
  .settings-tab.active { background: var(--accent-soft); color: var(--accent); }
  .settings-tab .st-name { font-size: 13.5px; font-weight: 600; }
  .settings-tab.active .st-name { font-weight: 700; }
  .settings-tab .st-desc { font-size: 11.5px; }

  /* Group headings: the rail's hairline above, one small-caps label below,
     inset to the item text. */
  .settings-group { display: flex; flex-direction: column; gap: 2px; }
  .settings-group + .settings-group::before { content: ''; display: block; height: 1px; background: var(--line); margin: 6px 10px 2px; }
  .settings-group-h { font-size: 10.5px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
    color: var(--ink-soft); opacity: 1; margin: 8px 10px 4px; }
  .settings-plan { margin-inline: 0; padding: 12px 10px 2px; }
}

/* Settings search: icon and clear button inside the field, the same height,
   corner and ground as the header's search (#cmdk-btn). */
.settings-find-wrap { position: relative; display: flex; align-items: center; flex-shrink: 0; margin: 0 0 4px; }
.settings-find-wrap > .rail-ico { position: absolute; inset-inline-start: 10px; width: 16px; height: 16px;
  color: var(--ink-soft); pointer-events: none; }
.settings-find-wrap .settings-find { width: 100%; height: var(--field-h); margin: 0; border-radius: var(--field-radius);
  border: 1px solid var(--line); background: var(--ground); color: var(--ink); font-size: 13px;
  padding-block: 0; padding-inline: 32px 30px; }
.settings-find-wrap .settings-find:hover { border-color: var(--accent); }
.settings-find-wrap .settings-find:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: transparent; }
.settings-find-wrap .settings-find::-webkit-search-cancel-button { display: none; }
.settings-find-clear { position: absolute; inset-inline-end: 6px; width: 20px; height: 20px; padding: 0;
  display: grid; place-items: center; border: none; border-radius: 50%; background: var(--line);
  color: var(--ink-soft); font-size: 14px; line-height: 1; cursor: pointer; }
.settings-find-clear:hover { background: var(--ink-soft); color: var(--panel); }
.settings-find-clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
@media (max-width: 640px) { .settings-find-wrap { display: none; } }

/* Ctrl+K: the search row is the field — icon, borderless input, Esc. */
.cmdk-search { display: flex; align-items: center; gap: 10px; height: 56px; flex: none; padding-inline: 16px;
  border-bottom: 1px solid var(--line); background: var(--panel); }
.cmdk-search > .rail-ico { width: 18px; height: 18px; flex: none; color: var(--ink-soft); }
.cmdk .cmdk-search input { flex: 1 1 auto; min-width: 0; height: 100%; padding: 0; border: 0; border-bottom: 0;
  background: transparent; color: var(--ink); font: inherit; font-size: 16px; outline: none; box-shadow: none; }
.cmdk .cmdk-search input:focus, .cmdk .cmdk-search input:focus-visible { outline: none; box-shadow: none; }
.cmdk-search kbd { flex: none; font: 11px ui-monospace, Menlo, monospace; padding: 1px 5px;
  border: 1px solid var(--line); border-radius: 5px; background: var(--ground); color: var(--ink-soft); }

/* Page headers (the title row under the top bar, inside each page): one
   title size, one subtitle, one 36px row that starts on the same line as the
   column titles beside it (12px from the top), one 24px side inset. Title rows
   that sit on the page carry no divider; the two toolbars that sit over a
   scrolling pane (.conv-head, .cal-head) keep theirs and share one 56px height. */
@media (min-width: 641px) {
  .page, .settings-body { padding: var(--col-top) var(--page-x) 24px; }
  .page > h2, .page-head, .settings-head { min-height: var(--col-title-h); }
  .page > h2 { display: flex; align-items: center; }
  .page h2, .settings-head h2, .cal-head h2 { font-size: 20px; font-weight: 700; letter-spacing: -0.01em; line-height: 1.2; }
  .page > h2, .page-head h2, .settings-head h2 { margin: 0; }
  .page-head, .settings-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 0; }
  .page > .page-sub, .settings-body > .page-sub, .settings-body > * > .page-sub {
    color: var(--ink-soft); font-size: 13.5px; line-height: 1.45; margin: 2px 0 16px; }
  .conv-head { height: var(--bar-h); flex-basis: var(--bar-h); padding-inline: var(--page-x) 16px; }
  .cal-head { min-height: var(--bar-h); padding: 8px var(--page-x); }
}

/* Settings: the page beside the list; on phones the list first, then the page
   full screen with Back (iOS/Android settings: small heading, a card of rows). */
.settings-page { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.settings-page > .settings-body { flex: 1; }
.settings-back { display: none; }
@media (max-width: 640px) {
  .settings-nav { flex-direction: column; align-items: stretch; overflow-x: visible; overflow-y: auto; border-bottom: none;
    padding: 12px 14px 24px; gap: 2px; flex: 1; background: var(--ground); }
  .settings-nav .settings-group-h, .settings-nav .settings-tab .st-desc, .settings-nav .settings-nomatch:not(.hidden) { display: block; }
  .settings-nav .settings-find-wrap { display: flex; margin-bottom: 8px; }
  .settings-nav .settings-find-wrap .settings-find { display: block; background: var(--panel); }
  /* The heading sits above the card, so the card is drawn on the rows. */
  .settings-nav .settings-group { display: flex; flex-direction: column; gap: 0; margin-bottom: 14px; }
  .settings-nav .settings-group-h { margin: 6px 0 0; padding: 0 4px 6px; background: transparent; }
  .settings-nav .settings-tab { flex: none; border-radius: 0; background: var(--panel); padding: 12px 14px; padding-inline-end: 30px;
    white-space: normal; color: var(--ink); position: relative;
    border: 1px solid var(--line); border-top: none; }
  .settings-nav .settings-tab.sf-first { border-top: 1px solid var(--line); border-radius: 12px 12px 0 0; }
  .settings-nav .settings-tab.sf-last { border-radius: 0 0 12px 12px; }
  .settings-nav .settings-tab.sf-first.sf-last { border-radius: 12px; }
  .settings-nav .settings-tab .st-name { font-size: 15px; font-weight: 600; }
  .settings-nav .settings-tab .st-desc { font-size: 12.5px; margin-top: 2px; color: var(--ink-soft); }
  .settings-nav .settings-tab::after { content: '\203A'; position: absolute; inset-inline-end: 12px; top: 50%; transform: translateY(-50%);
    color: var(--ink-soft); font-size: 20px; line-height: 1; }
  [dir="rtl"] .settings-nav .settings-tab::after { content: '\2039'; }
  .settings-nav .settings-tab.active, .settings-nav .settings-tab:hover { background: var(--panel); color: var(--ink); }
  .settings-nav .settings-tab:active { background: var(--ground); }
  .settings-nav .settings-tab.active .st-desc { color: var(--ink-soft); opacity: 1; }
  .settings-nav .settings-plan { flex-direction: row; justify-content: space-between; align-items: center; padding: 12px 4px; margin-top: 0; }
  body.m-settings-list .settings-page { display: none; }
  body:not(.m-settings-list) .settings-nav { display: none; }
  .settings-back { display: inline-flex; align-items: center; gap: 4px; align-self: flex-start; margin: 10px 8px 0; padding: 6px 10px;
    border: 0; background: transparent; color: var(--accent); font-weight: 600; font-size: 15px; }
  [dir="rtl"] .settings-back > span { display: inline-block; transform: scaleX(-1); }
}

/* ---------- UX plan Phase 4: small fixes ---------- */
/* Arabic: the mail filter arrows mirror, but stay hidden until there is more
   to scroll (an inline-block here used to show both, always). */
[dir="rtl"] .mail-filter-arrow { transform: translateY(calc(-50% - 4px)) scaleX(-1); }

/* 2FA banner on phones: the bold line and the button; the long reason wraps
   to three lines on a phone and pushed the top bar under it. */
@media (max-width: 640px) {
  #wa-2fa-banner .tfb-why { display: none; }
  /* Short now, so dot, line and button share one row. */
  #wa-2fa-banner { padding: 6px 12px; flex-wrap: nowrap; }
  #wa-2fa-banner .tfb-text, #wa-2fa-banner .tfb-cta, #wa-2fa-banner .tfb-dot { order: 0; }
  #wa-2fa-banner .tfb-text { flex: 1 1 auto; }
  #wa-2fa-banner .tfb-cta { margin-inline-start: 0; }
}

@media (min-width: 641px) {
  /* The brand's initial sits in a mark centred over the left bar, so the top
     bar and the bar below share one left edge; the name follows it. */
  #topbar { padding-inline-start: 0; }
  #topbar .brand { display: flex; align-items: center; }
  #topbar .brand::before {
    content: attr(data-initial); flex: none; display: grid; place-items: center;
    width: 32px; height: 32px; margin-inline: 24px 10px; border-radius: 9px;
    background: var(--accent); color: #fff; font-size: 16px; font-weight: 800; letter-spacing: 0;
  }
  body.rail-wide #topbar .brand { min-inline-size: 196px; }
  body.rail-wide #topbar .brand::before { margin-inline-start: calc(var(--col-gutter) + 10px); }

  /* Chat details tabs: same height as the chat header beside them. */
  .det-tabs { min-height: var(--bar-h); box-sizing: border-box; padding-block-start: 0; }
  .det-tab { display: flex; align-items: center; justify-content: center; }
  .det-more { align-self: center; margin-block-end: 0; }
}

/* ---------- Top bar, right side: line icons + one account menu ---------- */
.topbar-right { gap: 8px; }
.tb-icon { width: 36px; height: 36px; display: grid; place-items: center; border-radius: 10px; color: var(--ink-soft); padding: 0; }
.tb-icon:hover, .tb-icon[aria-expanded="true"] { background: var(--ground); color: var(--ink); }
.tb-icon .rail-ico { width: 20px; height: 20px; }
.tb-icon .notif-count { top: -1px; inset-inline-end: -3px; min-width: 18px; padding: 0 4px; font-size: 10px; line-height: 14px; text-align: center; border: 2px solid var(--panel); }
.me-wrap { position: relative; }
.me-btn { display: flex; align-items: center; gap: 8px; height: 36px; padding: 0 8px 0 3px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--panel); color: var(--ink); cursor: pointer; font: inherit; }
.me-btn:hover, .me-btn[aria-expanded="true"] { background: var(--ground); }
.me-av { position: relative; width: 28px; height: 28px; flex: none; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent); font-size: 11.5px; font-weight: 750; letter-spacing: .02em; }
.me-btn .me-av::after { content: ''; position: absolute; inset-inline-end: -1px; bottom: -1px; width: 9px; height: 9px; border-radius: 50%;
  background: var(--ink-soft); border: 2px solid var(--panel); opacity: .6; }
.me-btn.online .me-av::after { background: var(--good, #1e9e6a); opacity: 1; }
.me-btn .me-name { font-size: 13px; font-weight: 600; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.me-chev { width: 14px; height: 14px; stroke: var(--ink-soft); fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.me-menu { position: absolute; inset-inline-end: 0; top: 44px; width: 260px; z-index: 70; padding: 6px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px; box-shadow: 0 12px 32px rgba(0, 0, 0, .14);
  animation: md-rise .14s ease-out both; }
.me-menu-head { display: flex; align-items: center; gap: 10px; padding: 8px 8px 10px; border-bottom: 1px solid var(--line); margin-bottom: 4px; }
.me-menu-head b { display: block; font-size: 13.5px; }
.me-menu-head small { display: block; font-size: 12px; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 180px; }
.me-av.lg { width: 36px; height: 36px; font-size: 13px; }
.me-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px; border: 0; border-radius: 8px; background: none;
  color: var(--ink); font: inherit; font-size: 13.5px; text-align: start; cursor: pointer; }
.me-item:hover { background: var(--ground); }
.me-item .rail-ico { width: 18px; height: 18px; color: var(--ink-soft); }
.me-item.danger, .me-item.danger .rail-ico { color: var(--danger); }
.me-sep { height: 1px; background: var(--line); margin: 4px 6px; }
/* The status row: a dot, the state, and a hint that a tap switches it. */
.me-item.status-pill { border: 0; border-radius: 8px; font-weight: 600; }
.me-item.status-pill small { margin-inline-start: auto; font-size: 11.5px; font-weight: 400; color: var(--ink-soft); }
.me-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; margin-inline: 4px; background: var(--ink-soft); opacity: .6; }
.me-item.status-pill.online { color: var(--ink); }
.me-item.status-pill.online .me-dot { background: var(--good, #1e9e6a); opacity: 1; }
/* The header's menus open over everything on the page, the Get started panel
   (z-index 80) included. */
#topbar { position: relative; z-index: 90; }
@media (max-width: 640px) {
  .me-btn { padding: 0 3px; border: 0; }
  .me-btn .me-name, .me-btn .me-chev { display: none; }
  .me-menu { width: min(300px, calc(100vw - 20px)); }
}

/* ---------- Phone top bar: a real search bar ----------
   The brand shrinks to its square mark, and the search ("Search or jump to…")
   takes all the room between it and the icons on the right. */
@media (max-width: 640px) {
  #topbar { gap: 8px; }
  #topbar .brand { display: flex; align-items: center; flex: none; font-size: 0; }
  #topbar .brand::before {
    content: attr(data-initial); display: grid; place-items: center; width: 32px; height: 32px; border-radius: 9px;
    background: var(--accent); color: #fff; font-size: 16px; font-weight: 800;
  }
  #topbar .brand-dot { display: none; }
  .cmdk-btn { flex: 1 1 auto; min-width: 0; max-width: none; height: 36px; margin: 0; padding: 0 12px; border-radius: 10px; gap: 8px; }
  .cmdk-btn span { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; }
  .cmdk-btn kbd { display: none; }
  .topbar-right { flex: none; }
}

/* ---------- Chat column: tabs + one Filters panel (UX plan) ---------- */
.chatlist-head { position: relative; }
.filter-row.cf { display: block; overflow: visible; padding: 0; }
.cf-bar { display: flex; align-items: center; gap: 4px; }
.cf-tabs { flex: 1; min-width: 0; display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none; }
.cf-tabs::-webkit-scrollbar { display: none; }
.cf-tab { flex: none; height: 30px; padding: 0 10px; border: 0; border-radius: 8px; background: none; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 600; color: var(--ink-soft); white-space: nowrap; }
.cf-tab:hover { background: var(--ground); color: var(--ink); }
.cf-tab.active { background: var(--accent-soft); color: var(--accent); }
.cf-icon { position: relative; flex: none; width: 32px; height: 32px; display: grid; place-items: center; padding: 0;
  border: 1px solid transparent; border-radius: 8px; background: none; color: var(--ink-soft); cursor: pointer; }
.cf-icon:hover, .cf-icon[aria-expanded="true"] { background: var(--ground); color: var(--ink); }
.cf-icon.on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.cf-icon .rail-ico { width: 18px; height: 18px; }
.cf-count { position: absolute; top: -6px; inset-inline-end: -6px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 99px;
  background: var(--accent); color: #fff; font-size: 10px; font-weight: 700; line-height: 16px; text-align: center; border: 2px solid var(--panel); box-sizing: content-box; }
.cf-active { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; }
.cf-pill { display: inline-flex; align-items: center; gap: 2px; height: 26px; padding-inline: 10px 4px; border-radius: 99px;
  background: var(--accent-soft); color: var(--accent); font-size: 12px; font-weight: 600; max-width: 100%; }
.cf-pill button { width: 20px; height: 20px; border: 0; border-radius: 50%; background: none; color: inherit; cursor: pointer; font-size: 15px; line-height: 1; padding: 0; }
.cf-pill button:hover { background: rgba(0, 0, 0, .08); }
.cf-clear-all { border: 0; background: none; color: var(--ink-soft); font: inherit; font-size: 12px; text-decoration: underline; cursor: pointer; padding: 0 4px; }
.cf-pop { position: absolute; inset-inline: 10px; top: calc(100% - 2px); z-index: 20; padding: 12px; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--line); box-shadow: 0 12px 32px rgba(0, 0, 0, .14); animation: md-rise .14s ease-out both;
  max-height: min(70vh, 520px); overflow-y: auto; }
.cf-h { font-size: 10.5px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-soft); margin: 4px 2px 6px; }
.cf-h + .cf-chips, .cf-h + .cf-fields { margin-bottom: 12px; }
.cf-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.cf-fields { display: grid; gap: 8px; }
.cf-field { display: grid; grid-template-columns: 96px 1fr; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-soft); }
.cf-field select, .cf-field input { width: 100%; min-width: 0; height: 32px; }
.cf-field.cf-val { grid-template-columns: 1fr; padding-inline-start: 104px; }
@media (max-width: 640px) {
  .filter-row.cf { overflow: visible; }
  .cf-pop { position: fixed; inset-inline: 10px; top: auto; bottom: calc(70px + env(safe-area-inset-bottom)); z-index: 60; }
  .cf-field { grid-template-columns: 1fr; gap: 4px; }
  .cf-field.cf-val { padding-inline-start: 0; }
}

/* New chat and the Mail header: line icons in place of emoji. */
.new-chat-btn { color: var(--ink-soft); }
.new-chat-btn:hover { color: var(--accent); }
.new-chat-btn .rail-ico { width: 18px; height: 18px; }
@media (max-width: 640px) { .new-chat-btn { color: #fff; } .new-chat-btn .rail-ico { width: 24px; height: 24px; } }
.mail-hb-ico { display: inline-grid; place-items: center; }
.mail-hb-ico .rail-ico { width: 16px; height: 16px; }
.cf-pop .cf-field .phone-filter, .cf-pop .cf-field input { width: 100%; max-width: none; flex: 1 1 auto; }
.cf-tab { padding: 0 8px; }
/* When the tabs are wider than the column (Arabic, German), they scroll; a soft
   fade at the far edge says so instead of a word cut off mid-letter. */
.cf-tabs { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 18px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 18px), transparent); padding-inline-end: 14px; }
[dir="rtl"] .cf-tabs { -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 18px), transparent); mask-image: linear-gradient(to left, #000 calc(100% - 18px), transparent); }

/* ---------- Customers column: same tabs + Filters panel as the chat column ---------- */
.contacts-segments.cf { display: block; overflow: visible; padding: 0 12px 8px; position: relative; }
.contacts-filters.cf-host { padding: 0; position: relative; display: block; }
.contacts-filters.cf-host .cf-pop { top: -6px; inset-inline: 10px; }
.cf-tab .cseg-n { font-weight: 700; opacity: .7; margin-inline-start: 3px; font-size: 11.5px; }
.cf-chips .chip .cseg-n { opacity: .7; margin-inline-start: 3px; }
.cf-pop .cfilter-sel, .cf-pop .cfilter-inp { width: 100%; height: 32px; }
.cf-pop .cfilter-labels { display: flex; flex-wrap: wrap; gap: 6px; }
@media (max-width: 640px) { .contacts-filters.cf-host .cf-pop { top: auto; } }
@media (max-width: 640px) { .cf-pop .cfilter-sel, .cf-pop .cfilter-inp, .cf-pop .cf-field .phone-filter { height: auto; min-height: 40px; padding-block: 8px; line-height: 1.3; } }

/* ---------- the chat composer: one bar, every width ----------
 *
 *     [ +   ✨   Type a message ......................   🎤 ]
 *
 * WhatsApp's shape, drawn in this app's own style: the bar is the search
 * box's soft grey field with a 1px line, and every icon is a sidebar line icon
 * (.rail-ico) on a transparent 10px-rounded button with the sidebar's hover.
 *
 *  - + is the attachment menu, which also holds Take a photo (touch screens),
 *    Saved replies (starts a "/"), Private note, and the number to send from.
 *  - ✨ Suggest reply sits where WhatsApp keeps its sticker icon; in note mode
 *    it becomes the note icon, the one tap back out.
 *  - The mic turns into the green send button once there is text.
 *  - Long text: the bar grows line by line up to six lines, then the text
 *    scrolls inside it. The icons stay at the bottom, next to the last line,
 *    and the bar's corners stay the same however tall it gets.
 *
 * The old row of labelled tools is kept in the page but hidden: the bar's
 * icons and the menu hand over to those buttons, so note mode, the number to
 * send from and the suggestion behave exactly as before. Plan:
 * COMPOSER-PHONE-PLAN.html. Last in the file so it outranks the older rules.
 */
.composer { display: flex; flex-direction: column; gap: 6px; }
.composer-tools[hidden] { display: none; }
.composer-perm { font-size: 12px; color: var(--ink-soft); padding: 0 4px; }

.composer-row {
  align-items: flex-end; gap: 2px; padding: 3px;
  background: var(--ground); border: 1px solid var(--line); border-radius: 12px;
  transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
}
.composer-row:focus-within {
  background: var(--panel); border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(15, 122, 84, .12);
}
.composer-row:has(textarea.note-mode) { background: var(--note); border-color: #e5c95d; }
.composer-row:has(textarea.note-mode):focus-within { box-shadow: 0 0 0 3px rgba(229, 201, 93, .25); }

/* Icon buttons: the sidebar's look. */
.composer-row .attach-wrap { align-self: flex-end; }
.composer-row .attach-btn,
.composer-row .bar-btn,
.composer-row .voice-btn {
  width: 38px; height: 38px; flex: 0 0 38px; padding: 0;
  display: grid; place-items: center;
  border: none; border-radius: 10px; background: transparent;
  color: var(--ink-soft); cursor: pointer;
}
.composer-row .attach-btn:hover,
.composer-row .bar-btn:hover,
.composer-row .voice-btn:hover { background: rgba(127, 127, 127, .14); color: var(--ink); border-color: transparent; }
.composer-row .attach-btn:focus-visible,
.composer-row .bar-btn:focus-visible,
.composer-row .voice-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.composer-row .bar-btn.on { background: rgba(229, 201, 93, .35); color: var(--ink); }
.composer-row .bar-btn:disabled { opacity: .5; cursor: default; }
#ai-suggest-bar.busy .rail-ico { animation: spin 1.2s linear infinite; }

/* The text: borderless inside the bar, growing with what is typed. */
.composer .composer-row .composer-input-wrap,
.composer .composer-row .composer-input-wrap:focus-within,
.composer .composer-row .composer-input-wrap:has(textarea.note-mode) { background: transparent; border-radius: 0; }
.composer-row .composer-input-wrap textarea,
.composer-row .composer-input-wrap textarea:focus {
  border: none; box-shadow: none; background: transparent; border-radius: 0;
  padding: 9px 4px; min-height: 38px; max-height: 130px; line-height: 1.4;
}
.composer .composer-row .composer-ghost {
  border: none; border-radius: 0; padding: 9px 4px; min-height: 38px; max-height: 130px; line-height: 1.4;
}

/* Send: the app's green, a rounded square like the sidebar's buttons. */
.composer-row .composer-side { gap: 2px; align-self: flex-end; }
.composer-row .send-btn {
  width: 38px; min-width: 38px; height: 38px; min-height: 38px; padding: 0;
  display: grid; place-items: center; border-radius: 10px;
}
.composer-row .send-btn .send-ico { width: 18px; height: 18px; fill: currentColor; }
/* One button that changes: the mic while the box is empty, send once there is
   text. Only where a mic exists: in note mode, or where recording is not
   allowed, send stays. (An attachment has its own send in its preview.) */
.composer-row:has(textarea:not(:placeholder-shown)) .voice-btn { display: none; }
.composer-row:has(.voice-btn):has(textarea:placeholder-shown) .send-btn { display: none; }

/* The + menu, in the same icon style. */
.attach-menu .am-ico { display: grid; place-items: center; width: 22px; color: var(--ink-soft); }
.attach-menu .am-ico .rail-ico { width: 18px; height: 18px; }
.attach-menu .am-sep { height: 1px; margin: 4px 6px; background: var(--line); }
.attach-menu .am-key {
  margin-inline-start: auto; font: 11px ui-monospace, Menlo, monospace; padding: 1px 6px;
  border: 1px solid var(--line); border-radius: 4px; color: var(--ink-soft);
}
.attach-menu .am-check { margin-inline-start: auto; color: var(--accent); visibility: hidden; display: grid; }
.attach-menu .am-check .rail-ico { width: 16px; height: 16px; }
.attach-menu .am-from.on .am-check { visibility: visible; }
/* Taking a photo only means something where there is a camera to point. */
@media (hover: hover) and (pointer: fine) { .attach-menu .am-cam { display: none; } }

/* The list above never scrolls sideways: a react button or a long link
   poking past the edge drew a grey scrollbar across the chat. */
.msgs { overflow-x: hidden; }

@media (max-width: 640px) {
  .composer { padding: 6px 8px 8px; }
  /* An open chat gets the whole screen, as in WhatsApp: the top bar already
     hides; the bottom bar does too, and Back returns to the list and the bar.
     The details sheet, which only exists with a chat open, then reaches the
     bottom edge instead of stopping above a bar that is no longer there. */
  body.has-active-chat #rail { display: none; }
  body.m-details #details { bottom: 0; }
}

/* ---------- the top of an open chat: header, suggested labels, number banner ----------
 *
 * Same language as the composer bar below and the sidebar: line icons
 * (.rail-ico) on transparent buttons with 10px corners and the sidebar's hover.
 * On a phone the header keeps just the name, "via" the number, search and ✨
 * Summarize; the number banner shrinks to one line; a suggested label is one
 * line whose Add and ✕ always stay on screen.
 */
/* Shown and hidden by the existing rules above; only the look changes here. */
.conv-back, .conv-info, .conv-head-right .head-btn {
  align-items: center; justify-content: center; place-items: center; width: 36px; height: 36px; padding: 0;
  border: none; border-radius: 10px; background: transparent; color: var(--ink-soft); cursor: pointer;
}
.conv-head-right .head-btn { display: grid; }
.conv-back:hover, .conv-info:hover, .conv-head-right .head-btn:hover { background: var(--ground); color: var(--ink); }
.conv-back:focus-visible, .conv-info:focus-visible, .conv-head-right .head-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.conv-info .rail-ico, .conv-back .rail-ico, .head-btn .rail-ico { width: 20px; height: 20px; }
/* Summarize: the existing green outline pill, with the sparkles line icon. */
.head-ai { display: inline-flex; align-items: center; gap: 6px; }
.head-ai .rail-ico { width: 15px; height: 15px; }

/* Suggested labels: the icon in the same style. */
.lsug-src .rail-ico { width: 13px; height: 13px; color: var(--accent); display: block; }
.lsug .lsug-x { display: inline-grid; place-items: center; padding: 2px 6px; }
.lsug .lsug-x .rail-ico { width: 13px; height: 13px; }
.lsug-why { cursor: pointer; }
.lsug-why.open { white-space: normal; max-width: none; }

/* The number banner's one-line form exists only for a phone. */
.pb-short { display: none; }

@media (max-width: 640px) {
  .conv-head { gap: 6px; }
  .conv-head-right { gap: 2px; }
  .conv-kind { display: none; }
  /* Summarize becomes an icon button like search, beside it. */
  .conv-head-right .head-ai {
    width: 36px; height: 36px; padding: 0; justify-content: center;
    border: none; border-radius: 10px; background: transparent; color: var(--accent);
  }
  .conv-head-right .head-ai:hover { background: var(--ground); }
  .conv-head-right .head-ai .rail-ico { width: 20px; height: 20px; }
  .head-ai-label { display: none; }

  /* Suggested labels: one row each, two visible, Add and ✕ never pushed off. */
  .lsug-bar { flex-direction: column; flex-wrap: nowrap; max-height: 86px; padding: 6px 10px; }
  .lsug { flex: 0 0 auto; flex-wrap: nowrap; min-width: 0; }
  .lsug-why { flex: 1 1 auto; min-width: 0; max-width: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .lsug-why.open { white-space: normal; }
  .lsug .btn.sm { flex: 0 0 auto; }
  /* The name is short and is the point, so it never shrinks; the reason gives. */
  .lsug-name { flex: 0 0 auto; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* Compact actions: the phone-wide 44px button rule made each suggestion a
     big block and pushed the second one out of view. 32px still taps well. */
  .lsug { padding: 3px 3px 3px 10px; gap: 6px; align-items: center; }
  /* Add: a small pill. Not here: the phone-wide 44px button size, which made
     each suggestion a big block. */
  .lsug .btn.sm { min-height: 26px; height: 26px; padding: 0 10px; font-size: 12px; border-radius: 999px; align-self: center; }
  /* ✕: an icon, no box, like the header and composer icons. */
  .lsug .btn.sm.lsug-x { min-width: 26px; width: 26px; padding: 0; border: none; background: transparent; color: var(--ink-soft); }
  .lsug .btn.sm.lsug-x:hover { background: var(--ground); color: var(--ink); }
  /* The opened reason stays small, so tapping it reads more, not louder. */
  .lsug-why.open { font-size: 11.5px; line-height: 1.35; padding-block: 2px; }
  .lsug-bar { max-height: 92px; gap: 6px; }

  /* The number banner: one line, the button on the same line. */
  .phone-banner-inner { flex-wrap: nowrap; gap: 8px; padding: 7px 12px; font-size: 13px; line-height: 1.35; }
  .phone-banner-inner .pb-text { display: none; }
  .phone-banner-inner .pb-short {
    display: block; flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600;
  }
  .phone-banner-inner .pb-short[role="button"] { cursor: pointer; }
  .phone-banner-inner .pb-cta { padding: 4px 10px; font-size: 12.5px; border-radius: 8px; }
  .phone-banner-inner .pb-hint { font-size: 12px; white-space: nowrap; }
}

/* The + menu: a quiet heading over the numbers to send from. */
.attach-menu .am-title { padding: 6px 12px 2px; font-size: 11px; font-weight: 600; letter-spacing: .02em; text-transform: uppercase; color: var(--ink-soft); }

/* The + menu row: "Send from" and the chosen name on the right. */
.attach-menu .am-val { margin-inline-start: auto; padding-inline-start: 12px; color: var(--ink-soft); font-size: 12.5px; max-width: 50%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The + menu's two pages: the menu, and the numbers to send from. The second
   uses the same rows as the first, so it reads as part of the same menu. */
.attach-menu:not(.show-numbers) > .am-numbers { display: none; }
.attach-menu.show-numbers > :not(.am-numbers) { display: none; }
.attach-menu .am-chev { display: grid; margin-inline-start: 4px; color: var(--ink-soft); }
.attach-menu .am-chev .rail-ico { width: 16px; height: 16px; }
.attach-menu .am-back { font-weight: 600; }
.attach-menu .am-list { max-height: 280px; overflow-y: auto; }
.attach-menu .am-num-text { display: flex; flex-direction: column; min-width: 0; text-align: start; }
.attach-menu .am-num-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attach-menu .am-num-sub { font-size: 11.5px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.attach-menu .am-num.on .am-check { visibility: visible; }
.attach-menu .am-num.on .am-num-name { font-weight: 600; }
[dir="rtl"] .attach-menu .am-chev .rail-ico,
[dir="rtl"] .attach-menu .am-back .rail-ico { transform: scaleX(-1); }

/* Send from: the words stay on one line; the chosen name gives way instead.
   In the list, a long name is cut but its ↗ is not. */
.attach-menu .am-label { white-space: nowrap; flex: none; }
.attach-menu [data-act="from"] .am-val { min-width: 0; max-width: none; flex: 1 1 auto; text-align: end; }
.attach-menu .am-num-line { display: flex; align-items: baseline; gap: 4px; min-width: 0; }
.attach-menu .am-num-line .am-num-name { min-width: 0; }
.attach-menu .am-other { flex: none; color: var(--ink-soft); font-size: 12px; }
.attach-menu .am-num .am-num-text { flex: 1 1 auto; }
/* Room for "Send from" and the chosen name side by side. */
.attach-menu { width: min(280px, calc(100vw - 32px)); }
/* A suggested label's ✕: a plain icon at every width, like the header icons. */
.lsug .btn.sm.lsug-x { border: none; background: transparent; color: var(--ink-soft); box-shadow: none; }
.lsug .btn.sm.lsug-x:hover { background: var(--ground); color: var(--ink); }

/* ---------- chat details panel: one look with the composer and header ----------
 *
 * Phase 1 of DETAILS-PANEL-PLAN.html. Line icons (.rail-ico) on transparent
 * 10px buttons with the sidebar's hover; Pin and Archive one tap away in the
 * top bar (green when on); the ⋯ menu floats like the composer's + menu
 * instead of pushing the panel down; card carets are line chevrons.
 */
.det-tabs { align-items: center; gap: 2px; padding-inline-end: 8px; }
.det-tabs .det-tab { align-self: stretch; }
.det-icon {
  flex: none; width: 32px; height: 32px; padding: 0; margin: 0;
  display: grid; place-items: center; border: none; border-radius: 10px;
  background: transparent; color: var(--ink-soft); cursor: pointer;
}
.det-icon:hover, .det-icon[aria-expanded="true"] { background: var(--ground); color: var(--ink); }
.det-icon:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.det-icon.on { background: var(--accent-soft); color: var(--accent); }
.det-icon .rail-ico { width: 18px; height: 18px; }
.det-more-wrap { position: relative; flex: none; }
.det-tabs .det-more { margin: 0; font-size: inherit; }

.det-menu {
  position: absolute; top: calc(100% + 6px); inset-inline-end: 0; z-index: 80;
  width: 240px; display: flex; flex-direction: column; gap: 0;
  padding: 6px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--panel); box-shadow: 0 10px 28px rgba(16, 40, 34, .14);
}
.det-menu[hidden] { display: none; }
.det-menu button {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 9px 10px; border-radius: 8px; font-size: 13px; color: var(--ink);
}
.det-menu button:hover, .det-menu button:focus-visible { background: var(--ground); }
.det-menu .am-ico { display: grid; place-items: center; width: 20px; color: var(--ink-soft); flex: none; }
.det-menu .am-ico .rail-ico { width: 18px; height: 18px; }

/* Identity and waiting. */
.det-copy { display: inline-grid; place-items: center; padding: 3px; border-radius: 6px; }
.det-copy .rail-ico { width: 13px; height: 13px; }
.det-wait { display: flex; align-items: center; gap: 6px; }
.det-wait .rail-ico { width: 14px; height: 14px; flex: none; }

/* Cards: a line chevron that turns down when the card is open. */
.det-caret { display: grid; place-items: center; font-size: 0; }
.det-caret .rail-ico { width: 14px; height: 14px; stroke-width: 2; }
[dir="rtl"] .det-card:not(.open) .det-caret { transform: scaleX(-1); }
.det-cardaction, .crm-addfu { display: inline-flex; align-items: center; gap: 4px; }
.det-cardaction .rail-ico, .crm-addfu .rail-ico { width: 13px; height: 13px; stroke-width: 2.2; }

/* ---------- details panel, phase 2: the Overview content ---------- */
/* Assigned: a person chip with status, opening a searchable list. */
.det-strip { position: relative; }
.det-pop-wrap { position: relative; min-width: 0; }
.det-assign .det-pop-wrap { flex: 1; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.det-person {
  display: inline-flex; align-items: center; gap: 7px; max-width: 100%; min-width: 0;
  padding: 3px 8px 3px 3px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--panel); color: var(--ink); font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.det-person:hover:not(:disabled), .det-person[aria-expanded="true"] { border-color: var(--accent); }
.det-person:disabled { cursor: default; opacity: .85; }
.det-person-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.det-person-none { padding-inline-start: 6px; color: var(--ink-soft); font-weight: 600; }
.det-person-chev { display: grid; color: var(--ink-soft); }
.det-person-chev .rail-ico { width: 13px; height: 13px; transform: rotate(90deg); stroke-width: 2.2; }
.det-face {
  position: relative; flex: none; width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center; color: #fff; font-size: 9.5px; font-weight: 700;
}
.det-face-none { background: var(--chip, var(--ground)); border: 1px dashed var(--line); }
.det-st { position: absolute; inset-inline-end: -1px; bottom: -1px; width: 8px; height: 8px; border-radius: 50%; border: 2px solid var(--panel); background: #9aaba4; }
.det-st-online { background: #22a06b; }
.det-st-away { background: #e0a100; }
.det-linkbtn { border: none; background: none; padding: 2px 0; color: var(--accent); font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; }
.det-linkbtn:hover { text-decoration: underline; }

/* The popovers (Assigned and Labels): the + menu's look. */
.det-pop {
  position: absolute; top: calc(100% + 6px); inset-inline-start: 0; z-index: 70;
  width: min(268px, calc(100vw - 32px)); display: flex; flex-direction: column; gap: 2px;
  padding: 6px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--panel); box-shadow: 0 10px 28px rgba(16, 40, 34, .14);
}
.det-pop[hidden] { display: none; }
.det-labelpick.det-pop { top: auto; inset-inline: 16px auto; margin-top: -4px; }
.det-pop-search {
  width: 100%; padding: 8px 10px; margin-bottom: 4px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--ground); color: var(--ink); font: inherit; font-size: 13px;
}
.det-pop-search:focus { outline: none; border-color: var(--accent); background: var(--panel); }
.det-pop-list { max-height: 240px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.det-pop .det-pop-row, .det-pop .label-chip.det-pop-row {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 7px 8px;
  border: none; border-radius: 8px; background: transparent; color: var(--ink);
  font: inherit; font-size: 13px; font-weight: 400; text-align: start; cursor: pointer; opacity: 1;
}
.det-pop .det-pop-row:hover, .det-pop .det-pop-row:focus-visible { background: var(--ground); }
.det-pop .det-pop-row[hidden] { display: none; }
.det-pop-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.det-pop-check { display: grid; color: var(--accent); visibility: hidden; }
.det-pop-check .rail-ico { width: 15px; height: 15px; }
.det-pop .det-pop-row.on .det-pop-check { visibility: visible; }
.det-pop .det-pop-row.on .det-pop-name { font-weight: 600; }
.det-ldot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.det-pop-create { color: var(--accent) !important; font-weight: 600 !important; border-top: 1px solid var(--line) !important; border-radius: 0 0 8px 8px !important; }
.det-pop-create .rail-ico { width: 15px; height: 15px; }
.det-pop-hint, .det-pop-empty { padding: 6px 8px 2px; font-size: 11.5px; color: var(--ink-soft); }

/* Waiting: a pill, amber, red once past the reply target. */
.det-wait {
  display: inline-flex; width: auto; max-width: calc(100% - 32px);
  padding: 3px 10px; border-radius: 999px; font-weight: 600; border: none;
}
.det-wait.late { color: #b4322a; background: #fdecea; }
:root[data-theme="dark"] .det-wait.late { color: #f4a7a2; background: #3a1d1d; }

/* ⋯ menu: a value on the right (Backup On/Off) and a divider. */
.det-menu .det-menu-val { margin-inline-start: auto; color: var(--ink-soft); font-size: 12px; }
.det-menu .det-menu-sep { height: 1px; margin: 4px 6px; background: var(--line); }

/* Tickets: rows with their status, in the same rhythm as the other cards. */
#chat-tickets { display: flex; flex-direction: column; gap: 4px; }
#chat-tickets .ticket-mini { padding: 7px 9px; border-radius: 8px; border: 1px solid var(--line); cursor: pointer; }
#chat-tickets .ticket-mini:hover { background: var(--ground); }

/* Backup settings in the dialog. */
.bk-dialog { display: flex; flex-direction: column; gap: 10px; margin: 8px 0 4px; }
/* The Assigned list spans the strip (not just the space after "Assigned"),
   so it never sticks out past the panel; the panel never scrolls sideways. */
#details { overflow-x: hidden; }
.det-assign .det-pop-wrap { position: static; }
#det-assign-pop { top: 46px; inset-inline: 12px; width: auto; }
.det-pop-empty[hidden] { display: none; }

/* ---------- details panel, phase 3: the Customer tab ---------- */
.det-props .cp-panel { display: flex; flex-direction: column; gap: 14px; }
.cp-sec { display: flex; flex-direction: column; }
.cp-sec-head { display: flex; align-items: baseline; gap: 8px; padding: 12px 0 4px; }
.cp-sec-name { flex: 1; font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-soft); }
.cp-sec-sum { font-size: 11.5px; color: var(--ink-soft); }
.cp-sec-sum.bad { color: #b4322a; font-weight: 600; }
:root[data-theme="dark"] .cp-sec-sum.bad { color: #f4a7a2; }

/* One field per row: label on the left, value on the right; tap to edit. */
.cp-row { border-bottom: 1px dashed var(--line); }
.cp-row:last-child { border-bottom: none; }
.cp-row-head {
  display: flex; align-items: baseline; gap: 10px; width: 100%; padding: 8px 4px;
  border: none; border-radius: 8px; background: transparent; color: var(--ink);
  font: inherit; font-size: 13px; text-align: start; cursor: pointer;
}
.cp-row-head:hover:not(:disabled) { background: var(--ground); }
.cp-row-head:disabled { cursor: default; }
.cp-row-k { flex: 0 1 45%; min-width: 0; color: var(--ink-soft); overflow-wrap: anywhere; }
.cp-row-v { flex: 1; min-width: 0; text-align: end; font-weight: 600; overflow-wrap: anywhere; }
.cp-row-v.empty { color: var(--accent); font-weight: 600; }
.cp-row.missing .cp-row-k, .cp-row.missing .cp-row-v.empty { color: #b4322a; }
:root[data-theme="dark"] .cp-row.missing .cp-row-k, :root[data-theme="dark"] .cp-row.missing .cp-row-v.empty { color: #f4a7a2; }
.cp-row.editing { background: var(--ground); border-radius: 10px; border-bottom-color: transparent; margin: 2px 0; }
.cp-row.editing .cp-row-head { padding-bottom: 4px; }
.cp-row-edit { display: flex; flex-direction: column; gap: 8px; padding: 0 8px 10px; }
.cp-row-done { align-self: flex-end; }
.cp-toggle-empty { align-self: flex-start; margin-top: 4px; }

/* "also on <number>" in Overview. */
.det-also { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.det-also-chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; border: none; border-radius: 999px;
  background: var(--ground); color: var(--ink-soft); font: inherit; font-size: 11.5px; cursor: pointer;
}
.det-also-chip:hover { color: var(--ink); }
.det-also-chip .dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex: none; }

/* The way to the full contact page: its own row under the fields. */
.det-cust { margin-top: 20px; padding-top: 14px; border-top: 1px solid var(--line); }
.det-open-contact {
  display: inline-flex; align-items: center; gap: 8px; padding: 8px 10px; margin-inline-start: -10px;
  border-radius: 8px; font-size: 13px;
}
.det-open-contact:hover { background: var(--ground); text-decoration: none; }
.det-open-contact .rail-ico { width: 15px; height: 15px; flex: none; }
/* The value hugs the right edge; long values wrap within 60% of the row. */
.cp-row-head .cp-row-v { flex: 0 1 auto; max-width: 60%; margin-inline-start: auto; }
.cp-row-head .cp-row-k { flex: 1 1 auto; }

/* ---------- details panel, phase 4: the desktop fit ---------- */
#view { --dock-h: 0px; --composer-h: 0px; }
/* Wide screens have the room: 340px instead of 300px. */
@media (min-width: 1441px) { #details { flex-basis: 340px; } }
@media (min-width: 1221px) {
  /* The info button hides and shows the panel on a desktop too. */
  #conv-info { display: inline-flex; }
  body.details-hidden #details { display: none; }
  /* The briefing dock no longer covers the panel's last card. */
  #view:has(#brief-dock) #details { padding-bottom: calc(var(--dock-h) + 24px); }
  /* With the panel hidden, the dock sits above the composer, not on its buttons. */
  body.details-hidden .brief-dock { bottom: calc(var(--composer-h) + 14px); }
}
/* Below 1221px the panel is a slide-over, so the same applies to the dock. */
@media (max-width: 1220px) { .brief-dock { bottom: calc(var(--composer-h) + 14px); } }

/* Card header actions: "+ Add" in every card, in the panel's own text-action
   style (the same as "Assign to me", .det-linkbtn): green 12px text, no box,
   underlined on hover. The padding is only there to make it easy to hit. */
.det-cardaction {
  gap: 4px; padding: 6px 8px; margin-inline-end: 8px; border: none; border-radius: 6px;
  background: none; color: var(--accent); font-size: 12px; font-weight: 600;
}
.det-cardaction:hover { background: none; color: var(--accent); }
.det-cardaction:hover span { text-decoration: underline; }
.det-cardaction:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.det-cardaction:disabled { background: transparent; color: var(--ink-soft); }
.det-cardaction .rail-ico { width: 14px; height: 14px; stroke-width: 2.2; }
.det-cardaction.icon { padding: 5px 7px; }

/* After "+ Add": the deal and follow-up forms in the details panel. */
.det-linkbtn.danger { color: var(--danger); }
#details .crm-deal-foot { margin-block-start: 14px; }
#details .crm-composer { display: flex; flex-direction: column; gap: 10px; margin-block-start: 4px; }
#details .crm-composer > .crm-input { inline-size: 100%; box-sizing: border-box; }
#details .crm-composer .crm-field { margin-block-start: 0; }
#details .crm-composer .crm-chips { margin-block-start: -4px; }
.crm-fu-whenlabel { font-size: 12px; color: var(--ink-soft); }
.crm-fu-cancel { align-self: flex-start; color: var(--ink-soft); }
/* New ticket: the colour dot and the label name had no space between them. */
.label-checks .check-line { display: flex; align-items: center; gap: 8px; }

/* Deal value: the currency sits inside the box, so Value, Expected close and
   Owner start at the same edge. */
#details .crm-money { position: relative; gap: 0; }
#details .crm-money .crm-cur { position: absolute; inset-inline-start: 9px; pointer-events: none; }
#details .crm-money .crm-input { padding-inline-start: 44px; }

/* "also on <number>": the app's own line icon, not a text arrow. */
.det-also-chip .rail-ico { width: 11px; height: 11px; flex: none; opacity: .8; }

/* Back buttons: the same line icon as the chat screen's back, not a text arrow. */
.details-close, .nc-back { display: inline-grid; place-items: center; }
.details-close .rail-ico, .nc-back .rail-ico { width: 20px; height: 20px; }
.details-close { color: var(--ink-soft); border-radius: 10px; }
.details-close:hover { background: var(--ground); color: var(--ink); }
