/* ==========================================================================
   Salt CRM — application design system.

   Shell: a dark left rail (HighLevel's shape, which crews already know) with a
   modern surface treatment — flat surfaces, hairline borders, one accent, no
   heavy chrome. Mobile collapses the rail to a bottom tab bar so a crew lead
   can work the inbox one-handed.

   Every colour is a token defined once on :root and re-declared for dark. Never
   hard-code a colour in a component.
   ========================================================================== */

:root {
  /* Brand — Salt Services violet + gold. The rail keeps brand colour in both
     themes so the app is recognisable at a glance. */
  --rail:            #1d1436;
  --rail-2:          #251a45;
  --rail-line:       rgba(255,255,255,.08);
  --rail-text:       #cbc4de;
  --rail-text-dim:   #8e86a8;
  --rail-text-on:    #ffffff;

  --accent:          #e9a13b;
  --accent-hover:    #f2b053;
  --accent-ink:      #2b1a05;
  --accent-soft:     #fdf3e2;
  --accent-line:     #f0d5a4;

  /* Surfaces */
  --canvas:          #f6f5f1;
  --surface:         #ffffff;
  --surface-2:       #faf9f6;
  --surface-3:       #f1efe9;
  --border:          #e6e2d9;
  --border-strong:   #d4cec1;

  /* Type */
  --text:            #1b1430;
  --text-2:          #5b5372;
  --text-3:          #8a8299;

  /* Semantic */
  --ok:      #2f8f5b;  --ok-soft:     #e4f3ea;
  --warn:    #b87a12;  --warn-soft:   #fbeed6;
  --danger:  #c0392b;  --danger-soft: #fae8e6;
  --info:    #3b6fd4;  --info-soft:   #e7eefb;
  /* Text on those tints (a pill, a warning note). The same colour in the
     light theme; lighter where the background is dark, because #2f8f5b on a
     dark green tint is about 2:1 and hard to read (26 Oct 2026). Fills and
     borders keep using --ok/--warn/--danger/--info themselves. */
  --ok-text: var(--ok); --warn-text: var(--warn); --danger-text: var(--danger); --info-text: var(--info);

  /* Geometry */
  --r-sm: 7px; --r: 11px; --r-lg: 16px; --r-full: 999px;
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 20px; --s6: 24px; --s8: 32px; --s10: 40px;
  --iconrail-w: 76px;
  --frame-r: 14px;   /* the inset panel's rounded top-left corner */
  --panel-w: 232px;
  --topbar-h: 60px;

  --shadow-1: 0 1px 2px rgba(27,20,48,.06);
  --shadow-2: 0 4px 16px rgba(27,20,48,.09);
  --shadow-3: 0 16px 40px rgba(27,20,48,.16);

  --font: 'Instrument Sans', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --mono: 'Space Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --ease: cubic-bezier(.2, .8, .3, 1);
}

/* Tell the browser which theme the page is in, so the parts IT draws — the
   open list of a dropdown, date pickers, scrollbars — match. Without this a
   dark page got a light dropdown list with the page's light text in it:
   options there, but white on white. */
:root { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { color-scheme: dark; } }
option { background-color: var(--surface); color: var(--text); }

/* Dark theme: system preference, unless an explicit choice overrides it. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --canvas: #120e1e; --surface: #1a1530; --surface-2: #201a38; --surface-3: #282142;
    --border: #2d2649; --border-strong: #3d3560;
    --text: #f2f0f8; --text-2: #b3abc9; --text-3: #857d9c;
    --accent-soft: #3a2a12; --accent-line: #6b4f1c;
    --ok-soft: #14301f; --warn-soft: #322309; --danger-soft: #361915; --info-soft: #151f38;
    --ok-text: #6fd39b; --warn-text: #f0b055; --danger-text: #ff8a7a; --info-text: #8fb2ff;
    --shadow-1: 0 1px 2px rgba(0,0,0,.4); --shadow-2: 0 4px 16px rgba(0,0,0,.45); --shadow-3: 0 16px 40px rgba(0,0,0,.55);
  }
}
:root[data-theme="dark"] {
  --canvas: #120e1e; --surface: #1a1530; --surface-2: #201a38; --surface-3: #282142;
  --border: #2d2649; --border-strong: #3d3560;
  --text: #f2f0f8; --text-2: #b3abc9; --text-3: #857d9c;
  --accent-soft: #3a2a12; --accent-line: #6b4f1c;
  --ok-soft: #14301f; --warn-soft: #322309; --danger-soft: #361915; --info-soft: #151f38;
  --ok-text: #6fd39b; --warn-text: #f0b055; --danger-text: #ff8a7a; --info-text: #8fb2ff;
  --shadow-1: 0 1px 2px rgba(0,0,0,.4); --shadow-2: 0 4px 16px rgba(0,0,0,.45); --shadow-3: 0 16px 40px rgba(0,0,0,.55);
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; font-family: var(--font); font-size: 14.5px; line-height: 1.55;
  color: var(--text); -webkit-font-smoothing: antialiased;
  /* The frame. One brand-coloured surface behind the whole window, so the top
     bar and the icon rail read as a single continuous L rather than two
     separate bars meeting at a corner. The working area is laid on top of it
     as an inset panel, which is what gives the rounded corner something to be
     rounded against. */
  background: linear-gradient(158deg, var(--rail-2), var(--rail) 42%);
  background-repeat: no-repeat;
}
h1, h2, h3, h4 { margin: 0; font-weight: 650; line-height: 1.25; color: var(--text); letter-spacing: -.011em; }
a { color: inherit; }
button, input, select, textarea { font: inherit; color: inherit; }
[hidden] { display: none !important; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }
::selection { background: var(--accent); color: var(--accent-ink); }

/* ------------------------------------------------------------------ shell
   Two navigation columns, the shape Monday uses: a narrow icon rail for the
   top-level areas, then a panel listing what is inside the active area.

   The top bar and the rail are not painted at all — they let the body's brand
   gradient through, so together they form one unbroken frame down the left and
   across the top. Everything that changes as you work sits inside the inset
   panel, whose top-left corner is rounded away from that frame. The effect is
   a screen within a screen: the frame is the constant, the panel is the work.
   Account-level controls live in the top bar, not at the foot of the nav. */
body { display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }
.shell {
  flex: 1; display: grid; grid-template-columns: var(--iconrail-w) var(--panel-w) 1fr; min-height: 0;
}
/* The panel is display:none when collapsed, so it is not placed in the grid at
   all — leaving a third track here pushed the content into the zero-width one
   and the whole app went blank. Two tracks, because there are two items. */
.shell.panel-collapsed { grid-template-columns: var(--iconrail-w) 1fr; }

/* ------------------------------------------------------------- icon rail */
.iconrail {
  position: sticky; top: var(--topbar-h); align-self: start; z-index: 30;
  height: calc(100vh - var(--topbar-h)); height: calc(100dvh - var(--topbar-h));
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: var(--s3) var(--s2) var(--s3);
  background: none; border-right: 0;   /* continuous with the top bar */
}
.iconrail .mark { margin-bottom: var(--s4); }
.railbtn {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  width: 60px; padding: 8px 2px; border: 0; border-radius: var(--r);
  background: none; color: var(--rail-text-dim); cursor: pointer; position: relative;
  font-size: 10px; font-weight: 600; letter-spacing: .01em; text-align: center;
  transition: background .13s var(--ease), color .13s var(--ease);
}
.railbtn:hover { background: rgba(255,255,255,.08); color: var(--rail-text-on); }
.railbtn.on { background: rgba(233,161,59,.16); color: var(--rail-text-on); }
.railbtn.on::before {
  content: ''; position: absolute; left: -8px; top: 50%; transform: translateY(-50%);
  width: 3px; height: 22px; border-radius: 0 3px 3px 0; background: var(--accent);
}
.railbtn svg { width: 21px; height: 21px; stroke-width: 1.8; }
.railbtn .count {
  position: absolute; top: 4px; right: 8px; min-width: 17px; padding: 0 4px;
  border-radius: var(--r-full); background: var(--accent); color: var(--accent-ink);
  font-size: 10px; font-weight: 800; line-height: 16px;
}
.iconrail .spacer { flex: 1; }

/* ------------------------------------------------------------------ panel */
.panel-nav {
  position: sticky; top: var(--topbar-h); align-self: start; z-index: 25;
  height: calc(100vh - var(--topbar-h)); height: calc(100dvh - var(--topbar-h));
  display: flex; flex-direction: column; min-width: 0;
  background: var(--surface); border-right: 1px solid var(--border);
  overflow: hidden; border-top-left-radius: var(--frame-r);
}
.shell.panel-collapsed .panel-nav { display: none; }
.panel-head { display: flex; align-items: center; gap: var(--s2); padding: var(--s4) var(--s3) var(--s2) var(--s4); }
.panel-head h2 { flex: 1; font-size: 15px; font-weight: 680; letter-spacing: -.01em; }

.tenant {
  display: flex; align-items: center; gap: var(--s3); width: calc(100% - var(--s6));
  margin: 0 var(--s3) var(--s3); padding: var(--s2) var(--s3);
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--surface-2); color: var(--text); cursor: pointer; text-align: left;
  transition: background .13s var(--ease), border-color .13s var(--ease);
}
.tenant:hover { background: var(--surface-3); border-color: var(--border-strong); }
.tenant .av {
  flex: none; width: 26px; height: 26px; border-radius: 7px; display: grid; place-items: center;
  background: var(--rail); color: #fff; font-size: 11px; font-weight: 800;
}
.tenant .who { flex: 1; min-width: 0; }
.tenant .who b { display: block; font-size: 13px; font-weight: 650; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tenant .who span { display: block; font-size: 11px; color: var(--text-3); }
.tenant .chev { flex: none; color: var(--text-3); width: 15px; height: 15px; }

.panel-scroll { flex: 1; overflow-y: auto; padding: 0 var(--s3) var(--s4); }
.nav-item {
  display: flex; align-items: center; gap: var(--s3); width: 100%;
  padding: 8px var(--s3); border: 0; border-radius: var(--r-sm); background: none;
  color: var(--text-2); font-size: 13.5px; font-weight: 550; text-align: left;
  cursor: pointer; transition: background .12s var(--ease), color .12s var(--ease);
}
.nav-item:hover { background: var(--surface-3); color: var(--text); }
.nav-item.on { background: var(--accent-soft); color: var(--text); font-weight: 650; }
.nav-item .txt { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nav-item .count {
  flex: none; min-width: 20px; padding: 1px 6px; border-radius: var(--r-full);
  background: var(--accent); color: var(--accent-ink);
  font-size: 11px; font-weight: 700; text-align: center; font-variant-numeric: tabular-nums;
}
.nav-item .soon-tag {
  flex: none; font-size: 10px; font-weight: 700; letter-spacing: .04em;
  color: var(--text-3); border: 1px solid var(--border); border-radius: 5px; padding: 0 4px;
}
.nav-sep { height: 1px; background: var(--border); margin: var(--s3) var(--s2); }

/* ----------------------------------------------------------------- topbar */
.topbar {
  position: sticky; top: 0; z-index: 40; display: flex; align-items: center; gap: var(--s3);
  height: var(--topbar-h); padding: 0 var(--s4) 0 var(--s4); flex: none;
  background: none; border-bottom: 0;   /* the frame shows through */
}
.topbar .mark {
  width: 30px; height: 30px; font-size: 13px; display: grid; place-items: center;
  border-radius: 9px; background: var(--accent); color: var(--accent-ink); font-weight: 800;
}
.topbar .wordmark { color: var(--rail-text-on); }
.topbar .wordmark em { color: var(--accent); font-style: normal; }
.topbar .titlewrap { min-width: 0; padding-left: var(--s4); margin-left: var(--s2); border-left: 1px solid var(--rail-line); }
.icon-btn.on { background: var(--accent-soft); color: var(--text); }
.topbar h1 { font-size: 17px; font-weight: 650; color: var(--rail-text-on); }
.topbar .sub { color: var(--rail-text-dim); font-size: 12.5px; }

/* On the frame, controls are light-on-dark in both themes — the frame keeps
   brand colour whichever theme is set, so these cannot use the text tokens. */
.topbar .icon-btn { color: var(--rail-text); }
.topbar .icon-btn:hover { background: rgba(255,255,255,.10); color: var(--rail-text-on); }
.topbar .icon-btn.on { background: rgba(233,161,59,.2); color: var(--rail-text-on); }
.topbar .search {
  background: rgba(255,255,255,.09); border-color: var(--rail-line); color: var(--rail-text-dim);
}
.topbar .search input { color: var(--rail-text-on); }
.topbar .search input::placeholder { color: var(--rail-text-dim); }
.topbar .search kbd { background: rgba(255,255,255,.10); border-color: var(--rail-line); color: var(--rail-text-dim); }
.topbar .avatar { background: var(--accent); color: var(--accent-ink); }
.spacer { flex: 1; }

.icon-btn {
  display: grid; place-items: center; width: 34px; height: 34px; flex: none;
  border: 1px solid transparent; border-radius: var(--r-sm); background: none;
  color: var(--text-2); cursor: pointer; transition: background .13s var(--ease), color .13s var(--ease);
}
.icon-btn:hover { background: var(--surface-3); color: var(--text); }
#reload-btn.spin svg { animation: reload-spin .8s linear infinite; }
@keyframes reload-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { #reload-btn.spin svg { animation: none; opacity: .5; } }
.icon-btn svg { width: 18px; height: 18px; stroke-width: 1.9; }

.search {
  display: flex; align-items: center; gap: var(--s2); min-width: 0; max-width: 340px; flex: 1;
  padding: 7px var(--s3); border: 1px solid var(--border); border-radius: var(--r-full);
  background: var(--surface-2); color: var(--text-3);
}
.search input { flex: 1; min-width: 0; border: 0; background: none; outline: none; font-size: 13.5px; }
.search svg { width: 16px; height: 16px; flex: none; }
.search kbd {
  font-family: var(--font); font-size: 11px; padding: 1px 5px; border-radius: 5px;
  border: 1px solid var(--border); background: var(--surface); color: var(--text-3);
}

.avatar {
  width: 32px; height: 32px; border-radius: var(--r-full); flex: none; display: grid; place-items: center;
  background: var(--rail); color: #fff; font-size: 12px; font-weight: 700; cursor: pointer;
}

/* ---------------------------------------------------------------- content */
.content {
  padding: var(--s6); min-width: 0; overflow-x: hidden;
  /* Explicit, because the body behind it is now the brand frame, not canvas. */
  background: var(--canvas);
}
.content.flush { padding: 0; display: flex; min-height: 0; height: calc(100vh - var(--topbar-h)); height: calc(100dvh - var(--topbar-h)); }
/* With the panel collapsed the content is the left edge, so it takes the corner. */
.shell.panel-collapsed .content { border-top-left-radius: var(--frame-r); }

.page-head { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s5); flex-wrap: wrap; }
.page-head h2 { font-size: 20px; }

/* Cards, panels */
.card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--shadow-1);
}
.card-pad { padding: var(--s5); }
.card + .card { margin-top: var(--s4); }
.card-head {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s4) var(--s5); border-bottom: 1px solid var(--border);
}
.card-head h3 { font-size: 14.5px; font-weight: 650; }

/* Stat tiles */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--s4); margin-bottom: var(--s5); }
.stat { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--s4) var(--s5); }
.stat .k { font-size: 12px; font-weight: 600; color: var(--text-3); letter-spacing: .01em; }
.stat .v { font-size: 27px; font-weight: 700; letter-spacing: -.02em; margin-top: 2px; font-variant-numeric: tabular-nums; }
.stat .d { font-size: 12px; margin-top: 2px; color: var(--text-3); }
.stat .d.up { color: var(--ok-text); } .stat .d.down { color: var(--danger-text); }

/* Buttons */
.btn {
  display: inline-flex; align-items: center; gap: var(--s2); padding: 8px 14px;
  border: 1px solid var(--border-strong); border-radius: var(--r-sm); background: var(--surface);
  font-size: 13.5px; font-weight: 600; cursor: pointer; white-space: nowrap;
  transition: background .13s var(--ease), border-color .13s var(--ease), transform .1s var(--ease);
}
.btn:hover { background: var(--surface-3); }
.btn:active { transform: translateY(1px); }
.btn svg { width: 16px; height: 16px; stroke-width: 2; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn.primary:hover { background: var(--accent-hover); }
.btn.ghost { border-color: transparent; background: none; color: var(--text-2); }
.btn.ghost:hover { background: var(--surface-3); color: var(--text); }
.btn.sm { padding: 5px 10px; font-size: 12.5px; }

/* Pills / badges */
.pill {
  display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px; border-radius: var(--r-full);
  font-size: 11.5px; font-weight: 650; letter-spacing: .01em; white-space: nowrap;
  background: var(--surface-3); color: var(--text-2);
}
.pill.ok { background: var(--ok-soft); color: var(--ok-text); }
.pill.warn { background: var(--warn-soft); color: var(--warn-text); }
.pill.danger { background: var(--danger-soft); color: var(--danger-text); }
.pill.info { background: var(--info-soft); color: var(--info-text); }
.pill.accent { background: var(--accent-soft); color: var(--warn-text); border: 1px solid var(--accent-line); }
.dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }

/* Tables */
.tbl-wrap { overflow-x: auto; }
table.tbl { width: 100%; border-collapse: collapse; font-size: 13.5px; }
table.tbl th {
  text-align: left; font-size: 11.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-3); padding: var(--s3) var(--s5); border-bottom: 1px solid var(--border); white-space: nowrap;
}
table.tbl td { padding: var(--s3) var(--s5); border-bottom: 1px solid var(--border); vertical-align: middle; }
table.tbl tr:last-child td { border-bottom: 0; }
table.tbl tbody tr { transition: background .1s var(--ease); }
table.tbl tbody tr:hover { background: var(--surface-2); }
.cell-main { font-weight: 600; }
.cell-sub { color: var(--text-3); font-size: 12.5px; }

.ini {
  width: 30px; height: 30px; border-radius: var(--r-full); flex: none; display: grid; place-items: center;
  background: var(--surface-3); color: var(--text-2); font-size: 11.5px; font-weight: 700;
}
.row-id { display: flex; align-items: center; gap: var(--s3); }

/* Tabs (in-page) */
.seg { display: flex; gap: 2px; padding: 3px; border-radius: var(--r-sm); background: var(--surface-3); }
.seg button {
  padding: 5px 12px; border: 0; border-radius: 6px; background: none; color: var(--text-2);
  font-size: 13px; font-weight: 600; cursor: pointer;
}
.seg button.on { background: var(--surface); color: var(--text); box-shadow: var(--shadow-1); }

/* ------------------------------------------------------------------ inbox */
.inbox { display: grid; grid-template-columns: 340px 1fr; flex: 1; min-height: 0; min-width: 0; }
.thread-list { border-right: 1px solid var(--border); background: var(--surface); display: flex; flex-direction: column; min-height: 0; }
.thread-list-head { padding: var(--s4) var(--s4) var(--s3); border-bottom: 1px solid var(--border); }
.threads { overflow-y: auto; flex: 1; }
.thread {
  display: flex; gap: var(--s3); padding: var(--s3) var(--s4); width: 100%; text-align: left;
  border: 0; border-bottom: 1px solid var(--border); border-left: 3px solid transparent;
  background: none; cursor: pointer; transition: background .1s var(--ease);
}
.thread:hover { background: var(--surface-2); }
.thread.on { background: var(--accent-soft); border-left-color: var(--accent); }
.thread .body { flex: 1; min-width: 0; }
.thread .who { display: flex; align-items: baseline; gap: var(--s2); }
.thread .who b { font-size: 13.5px; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.thread .who time { margin-left: auto; font-size: 11.5px; color: var(--text-3); flex: none; }
.thread .prev {
  font-size: 12.5px; color: var(--text-2); margin-top: 1px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.thread.unread .who b { font-weight: 750; }
.thread.unread .prev { color: var(--text); }

.thread-view { display: flex; flex-direction: column; min-width: 0; background: var(--canvas); }
.thread-head { display: flex; align-items: center; gap: var(--s3); padding: var(--s3) var(--s5); background: var(--surface); border-bottom: 1px solid var(--border); }
.msgs {
  --z: var(--msg-zoom, 1);
  flex: 1; overflow-y: auto; padding: calc(var(--s5) * var(--z)); display: flex; flex-direction: column; gap: calc(var(--s3) * var(--z));
  /* Up and down only. A long unbroken link or email address used to widen
     the whole thread past the screen and make it slide sideways on a phone
     (Franklin, 5 Oct 2026). pan-y also keeps a two-finger pinch here for the
     message zoom rather than zooming the whole page. */
  overflow-x: hidden; touch-action: pan-y;
}
.msg { max-width: min(560px, 78%); min-width: 0; }
.msg .bubble {
  overflow-wrap: anywhere; word-break: break-word;
  padding: calc(9px * var(--z)) calc(var(--s4) * var(--z)); border-radius: var(--r-lg); font-size: calc(14px * var(--z));
  background: var(--surface); border: 1px solid var(--border); border-bottom-left-radius: var(--r-sm);
}
.msg .meta { font-size: calc(11px * max(var(--z), .85)); color: var(--text-3); margin-top: 4px; padding: 0 var(--s2); }
.msg.out { align-self: flex-end; }
.msg.out .bubble {
  background: var(--rail); color: #f1eefa; border-color: transparent;
  border-bottom-left-radius: var(--r-lg); border-bottom-right-radius: var(--r-sm);
}
.msg.out .meta { text-align: right; }
.msg.sys { align-self: center; max-width: 100%; }
.msg.sys .bubble {
  background: var(--surface-3); border-style: dashed; color: var(--text-2); font-size: calc(12.5px * var(--z));
  border-radius: var(--r-full); padding: 4px var(--s4);
}
.composer { padding: var(--s3) var(--s5) var(--s4); background: var(--surface); border-top: 1px solid var(--border); }
.composer .box {
  display: flex; align-items: flex-end; gap: var(--s2); padding: var(--s2) var(--s2) var(--s2) var(--s4);
  border: 1px solid var(--border-strong); border-radius: var(--r-lg); background: var(--surface-2);
}
.composer textarea {
  /* Grows with what is typed (it was one line, so a long text was a peephole —
     Franklin, 30 Oct 2026), up to a third of the screen, then scrolls. */
  flex: 1; border: 0; background: none; outline: none; resize: none; min-height: 38px; max-height: min(220px, 34vh); padding: 8px 0;
  line-height: 1.45; overflow-y: auto;
}
/* 16px on a phone: Safari zooms the page into any text box smaller than that. */
@media (pointer: coarse) {
  .composer textarea, .composer input { font-size: 16px; }
  /* About six lines on a phone: with the keyboard up, more would hide the conversation. */
  .composer textarea { max-height: 150px; }
}

/* The app never zooms as a whole; the messages pinch on their own (.msgs). */
html { touch-action: pan-x pan-y; }

/* ---------------------------------------------------------------- pipeline */
.board { display: flex; gap: var(--s4); overflow-x: auto; padding-bottom: var(--s3); }
.col { flex: none; width: 272px; display: flex; flex-direction: column; }
.col-head { display: flex; align-items: center; gap: var(--s2); padding: 0 var(--s1) var(--s3); }
.col-head b { font-size: 13px; font-weight: 650; }
.col-head .n { font-size: 11.5px; color: var(--text-3); font-weight: 600; }
.col-head .bar { flex: 1; height: 3px; border-radius: 3px; background: var(--border); }
.col-body { display: flex; flex-direction: column; gap: var(--s2); }
.deal {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
  padding: var(--s3) var(--s4); cursor: grab; box-shadow: var(--shadow-1);
  transition: box-shadow .13s var(--ease), transform .13s var(--ease);
}
.deal:hover { box-shadow: var(--shadow-2); transform: translateY(-1px); }
.deal[data-open-contact] { cursor: pointer; }
.deal:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.deal.dragging { opacity: .45; cursor: grabbing; }
.col.drop-over .col-body { outline: 2px dashed var(--accent); outline-offset: 4px; border-radius: var(--r); }
.col-body { min-height: 60px; }
.deal .t { font-size: 13.5px; font-weight: 620; }
.deal .m { display: flex; align-items: center; gap: var(--s2); margin-top: 6px; font-size: 12px; color: var(--text-3); }
.deal .v { margin-left: auto; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }

/* -------------------------------------------------------------- empty/soon */
.soon { display: grid; place-items: center; text-align: center; padding: var(--s10) var(--s5); }
.soon .ic {
  width: 52px; height: 52px; border-radius: var(--r-lg); display: grid; place-items: center;
  background: var(--accent-soft); color: var(--warn-text); margin-bottom: var(--s4);
}
.soon .ic svg { width: 24px; height: 24px; }
.soon h3 { font-size: 17px; margin-bottom: var(--s2); }
.soon p { max-width: 460px; color: var(--text-2); margin: 0 0 var(--s4); }

/* ------------------------------------------------------------ preview flag */
.preview-bar {
  display: flex; align-items: center; justify-content: center; gap: var(--s2);
  padding: 6px var(--s4); background: var(--accent-soft); border-bottom: 1px solid var(--accent-line);
  color: var(--warn-text); font-size: 12.5px; font-weight: 600; text-align: center;
}

/* --------------------------------------------------------------- mobile nav */
.tabbar { display: none; }
.scrim { display: none; }

@media (max-width: 900px) {
  /* minmax(0, …), not 1fr: a bare 1fr will not shrink below its longest
     preview line, so on a phone the list ran 47px off the glass and every
     conversation's time was cut off. */
  .inbox { grid-template-columns: minmax(0, 1fr); }
  .thread-list { min-width: 0; }
  .inbox .thread-view { display: none; }
  .inbox.open-thread .thread-list { display: none; }
  .inbox.open-thread .thread-view { display: flex; }
}

@media (max-width: 760px) {
  .shell { grid-template-columns: 1fr; }
  /* Bottom padding is NOT set here. Two hard-coded reservations used to live
     on this line — 88px on .content and 72px on .content.flush — written
     before --tabbar-h existed. The first is merely redundant; the second was
     the dead strip, because .content.flush IS the inbox, whose panes already
     reserve the bar for themselves. It added 72px of nothing underneath them.
     The single reservation is .content:not(.flush) further down. */
  .content { padding: var(--s4); }
  .topbar { padding: 0 var(--s3); gap: var(--s2); }
  /* The S gives its room to the reload button, so the business name keeps its. */
  .topbar .wordmark, .topbar .mark { display: none; }
  .topbar .titlewrap { padding-left: 0; margin-left: 0; min-width: 0; border-left: 0; }
  /* min-width:0 as well as overflow:hidden. The h1 is a flex item inside
     .title-btn, and a flex item will not shrink below its content width
     without it — so a long title like "Inbox · All conversations" did not
     ellipsis, it pushed itself under the bell and the chevron surfaced inside
     that button. The truncation was written; the shrink was what was missing. */
  .topbar h1 {
    font-size: 15.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    min-width: 0;
  }
  /* The chevron belongs to the title and must never outlive its bounds. */
  .title-btn { overflow: hidden; max-width: 100%; }
  /* With the phone button back in the header the row is one icon tighter;
     the title has to stop at its own box, not run under the icons. */
  .topbar .titlewrap { overflow: hidden; flex: 1 1 auto; }
  /* Settings is in the bottom tab bar on a phone. Two of them competed for a
     row that had no space to give. */
  /* Which business this is, under the title. It was hidden on the strength of
     a chip row that no page draws, so a phone never said whose data it showed. */
  .topbar .sub {
    display: block; font-size: 11.5px; line-height: 1.2;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .topbar h1 { line-height: 1.2; }
  .content.flush { height: auto; flex: 1; }
  /* An open conversation fills exactly the screen under the header, and only
     the messages scroll — so the contact's name stays at the top and the
     reply box stays at the bottom (Franklin, 5 Oct 2026). With height:auto
     the whole page scrolled instead and took both with it. */
  .content.flush:has(.inbox.open-thread) {
    flex: none; height: calc(100vh - var(--topbar-h)); height: calc(100dvh - var(--topbar-h));
    overflow: hidden;
  }
  .inbox.open-thread, .inbox.open-thread .thread-view { min-height: 0; height: 100%; }
  .inbox.open-thread .thread-head { flex: none; padding: var(--s2) var(--s3); }
  .inbox.open-thread .composer { flex: none; }
  .inbox.open-thread .msgs { min-height: 0; overscroll-behavior: contain; }
  .topbar .search { display: none; }
  .stats { grid-template-columns: 1fr 1fr; gap: var(--s3); }

  /* One column. The icon rail becomes the bottom tab bar and the panel is
     dropped entirely — its items open as a bottom sheet from the title, which
     beats a horizontally scrolling strip nobody can see the end of. */
  .shell, .shell.panel-collapsed { display: flex; flex-direction: column; }
  .iconrail, .panel-nav { display: none; }
  .content { flex: 1; min-height: 0; }
  /* Full width now, so the panel takes both top corners off the frame rather
     than the single left one it has on a desktop. */
  .content, .shell.panel-collapsed .content {
    border-radius: var(--frame-r) var(--frame-r) 0 0;
  }

  /* Bottom tab bar — the primary mobile navigation.
     It is fixed, so nothing below it in normal flow is reachable. Everything
     that ends at the bottom of the screen has to reserve its height, or it
     lays out underneath: the reply box in a thread did exactly that, sitting
     behind the tabs where it could be seen but not used. --tabbar-h is that
     reservation, in one place, so the next bottom-anchored thing does not
     have to rediscover it. */
  :root { --tabbar-h: calc(56px + env(safe-area-inset-bottom)); }
  .tabbar {
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    background: var(--surface); border-top: 1px solid var(--border);
    padding: 4px 4px calc(4px + env(safe-area-inset-bottom));
  }
  /* Reserving on the shell was too blunt: it left a dead strip under every
     scrolling list, because a list should scroll UNDER the tabs, not stop
     short of them. The reservation belongs inside each scroll container as
     bottom padding — the last row clears the bar, and nothing is cut off —
     and on anything genuinely pinned to the bottom, like the reply box.

     The tab bar and the sheet are siblings of the shell, so neither is
     affected by what happens inside it. */
  /* The scrolling list clears the bar with its own last row, so nothing is
     hidden and there is no dead strip. */
  .threads { padding-bottom: var(--tabbar-h); }
  /* The reply box is the one thing genuinely pinned to the bottom. .msgs sits
     directly above it in the same flex column, so the composer's padding is
     the whole reservation — adding it to .msgs as well would double up. */
  .composer { padding-bottom: calc(var(--s4) + var(--tabbar-h)); }
  /* Ordinary pages scroll in .content. The inbox does NOT — it is
     .content.flush with its own panes — and padding there would put the strip
     straight back. */
  .content:not(.flush) { padding-bottom: var(--tabbar-h); }
  .tabbar button {
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 6px 2px; border: 0; background: none; color: var(--text-3);
    font-size: 10.5px; font-weight: 600; cursor: pointer; border-radius: var(--r-sm);
    position: relative; min-height: 48px; justify-content: center;
  }
  .tabbar button svg { width: 21px; height: 21px; stroke-width: 1.9; }
  .tabbar button.on { color: var(--accent); }
  .tabbar button .count {
    position: absolute; top: 2px; left: 50%; margin-left: 4px;
    min-width: 17px; padding: 0 4px; border-radius: var(--r-full);
    background: var(--danger); color: #fff; font-size: 10px; font-weight: 700; line-height: 16px;
  }
  }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; }
}

/* --------------------------------------------------- mobile: tables → cards
   A data table cannot be read on a 375px screen: columns overflow and phone
   numbers wrap three lines deep. Below the breakpoint each row becomes a card
   and each cell carries its own label via data-label, so the same markup
   serves both layouts and nothing is hidden from a phone user. */
@media (max-width: 760px) {
  .page-head { gap: var(--s2); }
  .page-head h2 { font-size: 18px; width: 100%; }
  .page-head .btn { flex: 1; justify-content: center; }

  table.tbl, table.tbl tbody, table.tbl tr, table.tbl td { display: block; width: 100%; }
  table.tbl thead { display: none; }
  table.tbl tr {
    border: 1px solid var(--border); border-radius: var(--r); margin-bottom: var(--s3);
    padding: var(--s3) var(--s4); background: var(--surface);
  }
  table.tbl tbody tr:hover { background: var(--surface); }
  table.tbl td { border: 0; padding: 3px 0; display: flex; align-items: center; gap: var(--s3); }
  table.tbl td::before {
    content: attr(data-label); flex: none; min-width: 82px;
    font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text-3);
  }
  /* The identity cell leads the card: no label, full width. */
  table.tbl td:first-child { padding-bottom: var(--s2); margin-bottom: var(--s2); border-bottom: 1px dashed var(--border); }
  table.tbl td:first-child::before { content: none; }
  /* A name and its description are two lines, not two columns: side by side,
     an automation's name was squeezed to one word per line. */
  table.tbl td:first-child { flex-wrap: wrap; row-gap: 2px; }
  table.tbl td:first-child > .cell-sub { flex-basis: 100%; }
  /* A card's buttons wrap under its title instead of running off the edge
     (the Forms page lost "Fields" past the right side of the screen). */
  .card-head { flex-wrap: wrap; row-gap: var(--s2); }
  .card-head h3 { min-width: 0; overflow-wrap: anywhere; }
  /* Key/value settings tables keep their row header instead. */
  table.tbl tr > th { display: block; padding: 0 0 2px; border: 0; }

  .board { margin: 0 calc(var(--s4) * -1); padding: 0 var(--s4) var(--s3); }
  .col { width: 246px; }
  .stat .v { font-size: 23px; }
}


/* ====================================================== menus and sheets
   One component, two presentations: an anchored dropdown on a pointer screen,
   a bottom sheet on a phone where a thumb reaches the bottom of the glass. */
.sheet-backdrop {
  position: fixed; inset: 0; z-index: 80; background: rgba(12, 8, 24, .45);
  /* A finger on the dimmed page must not scroll the page under it. */
  touch-action: none;
  opacity: 0; pointer-events: none; transition: opacity .18s var(--ease);
}
.sheet-backdrop.open { opacity: 1; pointer-events: auto; }
.sheet {
  position: fixed; z-index: 90; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--shadow-3);
  min-width: 264px; max-width: min(340px, calc(100vw - 24px)); padding: var(--s2);
  opacity: 0; transform: translateY(-6px) scale(.98); pointer-events: none;
  transition: opacity .14s var(--ease), transform .14s var(--ease);
  max-height: calc(100dvh - 96px); overflow-y: auto;
  /* Reaching the end of a short list must not hand the scroll to the page behind. */
  overscroll-behavior: contain;
}
/* While a sheet is open the page behind stays put (Franklin, 8 Oct 2026:
   swiping the handle slid the window behind instead of the sheet). */
html.sheet-locked, html.sheet-locked body { overflow: hidden; }
.sheet-grab { display: none; }
.sheet.open { opacity: 1; transform: none; pointer-events: auto; }
.sheet-title {
  font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-3); padding: var(--s3) var(--s3) var(--s2);
}
.menu-head { display: flex; align-items: center; gap: var(--s3); padding: var(--s3); }
.menu-head .who { min-width: 0; }
.menu-head .who b { display: block; font-size: 14px; font-weight: 650; }
.menu-head .who span { display: block; font-size: 12px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; }
.menu-item {
  display: flex; align-items: center; gap: var(--s3); width: 100%;
  padding: 9px var(--s3); border: 0; border-radius: var(--r-sm); background: none;
  color: var(--text); font-size: 14px; font-weight: 550; text-align: left; cursor: pointer;
}
.menu-item:hover { background: var(--surface-3); }
a.menu-item { text-decoration: none; box-sizing: border-box; }
.menu-item .ext { flex: none; width: 14px; height: 14px; color: var(--text-3); }
.menu-item.on { background: var(--accent-soft); font-weight: 650; }
.menu-item .av {
  flex: none; width: 26px; height: 26px; border-radius: 7px; display: grid; place-items: center;
  background: var(--rail); color: #fff; font-size: 11px; font-weight: 800;
}
.menu-item .txt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-item .sub { font-size: 11.5px; color: var(--text-3); font-weight: 500; }
.menu-item .tick { flex: none; color: var(--accent); width: 16px; height: 16px; }
.menu-item .count {
  flex: none; min-width: 20px; padding: 1px 6px; border-radius: var(--r-full);
  background: var(--accent); color: var(--accent-ink); font-size: 11px; font-weight: 700; text-align: center;
}
.menu-item .soon-tag {
  flex: none; font-size: 10px; font-weight: 700; color: var(--text-3);
  border: 1px solid var(--border); border-radius: 5px; padding: 0 4px;
}
.menu-item svg:first-child { flex: none; width: 18px; height: 18px; stroke-width: 1.9; color: var(--text-2); }
.menu-sep { height: 1px; background: var(--border); margin: var(--s2) var(--s1); }

/* The top-bar title: page over business. On a phone it is the business switcher. */
.title-btn {
  display: grid; justify-items: start; min-width: 0;
  border: 0; background: none; padding: 0; margin: 0; text-align: left; cursor: default; color: inherit;
}
.title-btn .chev { display: none; }

@media (max-width: 760px) {
  /* The business leads, with the arrow beside it, because the business is what
     the arrow changes. The page's name drops to the small line. */
  .title-btn { grid-template-columns: minmax(0, auto) auto; justify-content: start; align-items: center; column-gap: 5px; }
  .title-btn { cursor: pointer; }
  .title-btn #view-sub { grid-row: 1; grid-column: 1; max-width: 100%; font-size: 15.5px; font-weight: 650; color: var(--rail-text-on); }
  .title-btn #view-title { grid-row: 2; grid-column: 1 / -1; max-width: 100%; font-size: 11.5px; font-weight: 400; color: var(--rail-text-dim); }
  .title-btn .chev { display: block; grid-row: 1; grid-column: 2; width: 17px; height: 17px; color: var(--rail-text-on); }
  .sheet {
    left: 0; right: 0; bottom: 0; top: auto !important; width: 100%; max-width: none;
    border-radius: var(--r-lg) var(--r-lg) 0 0; border-bottom: 0;
    padding: var(--s2) var(--s3) calc(var(--s4) + env(safe-area-inset-bottom));
    transform: translateY(100%); max-height: 80dvh;
  }
  .sheet.open { transform: none; }
  /* The handle is a real control: a finger-sized strip across the top of the
     sheet that only ever drags the sheet. touch-action: none tells the
     browser this strip is not for scrolling, so iOS hands the whole gesture
     to the drag code instead of scrolling the page behind. */
  .sheet-grab {
    display: flex; align-items: center; justify-content: center;
    position: sticky; top: calc(-1 * var(--s2)); z-index: 1;
    height: 28px; margin: calc(-1 * var(--s2)) calc(-1 * var(--s3)) var(--s1);
    background: var(--surface); touch-action: none; cursor: grab;
  }
  .sheet-grab span { width: 40px; height: 5px; border-radius: 5px; background: var(--border-strong); }
  .sheet-grab:active { cursor: grabbing; }
  .sheet { transition: transform .22s ease, opacity .22s ease; }
}

/* ------------------------------------------------- inbox: message states
   Delivery is shown, never implied. A failed send keeps its bubble and states
   the carrier's reason in plain language, because a message that silently
   vanished is the failure mode this whole project exists to end. */
.msg-fail { color: var(--danger-text); font-weight: 600; }
.msg .bubble.media { padding: 4px; overflow: hidden; }
.msg .mms { display: block; max-width: min(calc(260px * var(--z)), 100%); height: auto; border-radius: var(--r); }
.msg .mms-file { display: inline-block; padding: 6px var(--s3); font-size: 13px; text-decoration: underline; }
.msg.note { align-self: center; max-width: min(520px, 86%); }
.msg.note .bubble {
  background: var(--warn-soft); border: 1px dashed var(--accent-line); color: var(--text);
  border-radius: var(--r); font-size: calc(13.5px * var(--z, 1));
}
.msg.note .meta { text-align: center; }
.msg.sys .rec { display: block; width: 260px; max-width: 100%; height: 34px; margin: var(--s2) auto 0; }
.msg.sys .transcript {
  margin-top: var(--s2); padding: var(--s2) var(--s4); max-width: min(520px, 100%); overflow-wrap: anywhere;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
  font-size: 13px; color: var(--text-2); font-style: italic;
}
.thread .star-on { color: var(--accent); font-size: 13px; align-self: center; }
.icon-btn.on { color: var(--accent); }

.composer-row { display: flex; align-items: center; gap: var(--s3); margin-top: var(--s2); }
/* This row sits above the reply box, so its space goes underneath (Franklin, 9 Nov 2026). */
.composer-row.wire-pick { margin-top: 0; margin-bottom: var(--s3); flex-wrap: wrap; row-gap: var(--s2); }
/* Row buttons: whitespace alone left them under 3px apart, and touching once they wrapped. */
.actions-cell { display: flex; flex-wrap: wrap; gap: var(--s2); }
td > .btn + .btn { margin-left: var(--s1); }
.composer-blocked {
  margin: 0; padding: var(--s3) var(--s4); border-radius: var(--r);
  background: var(--danger-soft); color: var(--danger-text); font-size: 13px; font-weight: 550;
}
.check { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-2); cursor: pointer; }
.check input { accent-color: var(--accent); }
.ta {
  display: block; width: 100%; margin-top: var(--s1); padding: var(--s2) var(--s3);
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface);
  color: var(--text); font: inherit; font-size: 13.5px; resize: vertical;
}
.ta.mono { font-family: var(--mono); font-size: 12px; line-height: 1.55; }
/* Website chat rehearsal: question, reply, what passing looks like. */
.rehearse-row { display: grid; gap: var(--s1); padding: var(--s3) 0; border-bottom: 1px solid var(--border); font-size: 13.5px; }
.rehearse-row:last-child { border-bottom: 0; }
.rehearse-row .rq { font-weight: 600; }
.rehearse-row .ra { padding: var(--s2) var(--s3); background: var(--surface-3); border-radius: var(--r-sm); overflow-wrap: anywhere; }
.rehearse-row .rp { color: var(--text-2); font-size: 12.5px; display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; }
.rehearse-row.fail .ra { box-shadow: inset 3px 0 0 var(--danger); }
/* Auto-sized by grow(); see the .field-grid textarea note. */
.ta.grow { overflow-y: auto; }

/* -------------------------------------------------------- phase 2: CRM */
.toolbar { display: flex; gap: var(--s3); align-items: center; margin-bottom: var(--s4); flex-wrap: wrap; }
.search.wide { max-width: 360px; background: var(--surface); }
select.mini, input.mini {
  font: inherit; font-size: 13px; padding: 6px var(--s3);
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface); color: var(--text);
}
table.tbl tr[data-open-contact] { cursor: pointer; }
.chips { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; }
.warn-note {
  margin: var(--s4) 0 0; padding: var(--s3) var(--s4); border-radius: var(--r);
  background: var(--warn-soft); color: var(--warn-text); font-size: 13px; font-weight: 550;
}
.mini-row { display: flex; align-items: center; gap: var(--s3); padding: var(--s2) 0; border-bottom: 1px solid var(--border); font-size: 13.5px; }
.mini-row:last-child { border-bottom: 0; }
.mini-row b { margin-left: auto; font-variant-numeric: tabular-nums; }
.note-row { padding: var(--s3) 0; border-bottom: 1px solid var(--border); }
.note-row p { margin: 0 0 2px; font-size: 13.5px; white-space: pre-wrap; }
.tl-row { display: flex; gap: var(--s3); padding: var(--s2) 0; font-size: 13.5px; }
.tl-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; margin-top: 7px; background: var(--border-strong); }
.tl-dot.message { background: var(--accent); }
.tl-dot.call { background: var(--info); }
.tl-dot.note { background: var(--warn); }
.check .done { text-decoration: line-through; color: var(--text-3); }
.ini.lg { width: 40px; height: 40px; font-size: 14px; }

/* Contact detail as a drawer — the list stays put behind it. */
.drawer-scrim { position: fixed; inset: 0; z-index: 70; background: rgba(12, 8, 24, .4); }
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 75; width: min(460px, 100vw);
  display: flex; flex-direction: column;
  background: var(--surface); border-left: 1px solid var(--border); box-shadow: var(--shadow-3);
  animation: drawer-in .18s var(--ease);
}
@keyframes drawer-in { from { transform: translateX(16px); opacity: .6; } to { transform: none; opacity: 1; } }
.drawer-head { display: flex; align-items: center; gap: var(--s3); padding: var(--s4) var(--s5); border-bottom: 1px solid var(--border); }
.drawer-head h3 { font-size: 16px; }
.drawer-body { flex: 1; overflow-y: auto; padding: var(--s4) var(--s5) var(--s8); }
/* What you can DO with this person, above what you can read about them. */
.drawer-actions { display: flex; flex-wrap: wrap; gap: var(--s2); padding: var(--s3) var(--s5); border-bottom: 1px solid var(--border); }
.drawer-actions .btn svg { width: 15px; height: 15px; }
.pill-x {
  border: 0; background: none; padding: 0 0 0 4px; margin: 0; font: inherit; line-height: 1;
  color: var(--text-3); cursor: pointer;
}
.pill-x:hover { color: var(--danger-text); }
.deal-row .deal-name { min-width: 0; flex: 1; }
.deal-row select { max-width: 46%; }
.deal-row b { margin-left: 0; }
.task-form { display: flex; flex-direction: column; gap: var(--s2); padding: var(--s2) 0 var(--s3); border-bottom: 1px solid var(--border); }
.task-form-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); font-size: 13px; color: var(--text-2); }
.task-form-row label { display: flex; align-items: center; gap: var(--s2); }
/* Timeline rows are buttons: tapping one opens the conversation. */
button.tl-row.tl-link {
  width: calc(100% + 2 * var(--s2)); margin: 0 calc(-1 * var(--s2)); padding: var(--s2);
  border: 0; border-radius: var(--r); background: none; font: inherit; color: inherit; text-align: left; cursor: pointer;
}
button.tl-row.tl-link:hover { background: var(--surface-2); }
/* A section inside a card or the drawer. The rule below already assumed this
   divider existed; nothing defined it, so every heading sat flush against the
   section above. */
.subpanel { margin-top: var(--s5); padding-top: var(--s4); border-top: 1px solid var(--border); }
.subpanel > h3 { font-size: 14px; margin: 0 0 var(--s2); }
.drawer-body .subpanel:first-of-type { border-top: 0; margin-top: var(--s4); padding-top: 0; }
@media (max-width: 760px) {
  .drawer { width: 100vw; border-left: 0; }
  /* flex-basis too: .search is flex 1 1 0, so width alone left Contacts with
     a 32px search box beside its two dropdowns. */
  .toolbar .search.wide { max-width: none; width: 100%; flex: 1 1 100%; }
  .toolbar select.mini { flex: 1 1 0; min-width: 0; }
  @keyframes drawer-in { from { transform: translateY(24px); opacity: .6; } to { transform: none; opacity: 1; } }
}

/* ---------------------------------------------------- phase 3: automations */
.recipe-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: var(--s4); }
.recipe { border: 1px solid var(--border); border-radius: var(--r); padding: var(--s4); background: var(--surface-2); }
.recipe .t { font-weight: 650; font-size: 13.5px; margin-bottom: 4px; }
.recipe p { margin: 0 0 var(--s3); font-size: 12.5px; color: var(--text-2); }

.step-row {
  display: flex; gap: var(--s3); align-items: flex-start;
  padding: var(--s3); border: 1px solid var(--border); border-radius: var(--r);
  background: var(--surface-2); margin-bottom: var(--s2);
}
.step-row .step-n {
  flex: none; width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent); color: var(--accent-ink); font-size: 11px; font-weight: 800;
}
.step-row b { font-size: 13px; }
.drawer.wide { width: min(620px, 100vw); }
.drawer-foot {
  position: sticky; bottom: 0; display: flex; gap: var(--s3); align-items: center;
  padding: var(--s4) 0 0; margin-top: var(--s6); border-top: 1px solid var(--border); background: var(--surface);
}

/* Run log — one line per step, in the words a person would use. */
.runlog { display: flex; flex-direction: column; }
.log-row { display: flex; gap: var(--s3); padding: var(--s2) 0; font-size: 13.5px; align-items: flex-start; }
.log-row + .log-row { border-top: 1px solid var(--border); }
.log-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; margin-top: 7px; background: var(--ok); }
.log-row.bad .log-dot { background: var(--danger); }
.log-row.bad > div > div:first-child { color: var(--danger-text); font-weight: 600; }
.quote {
  margin-top: var(--s2); padding: var(--s2) var(--s3); border-left: 2px solid var(--accent);
  background: var(--surface-2); font-size: 12.5px; color: var(--text-2);
}

/* ------------------------------------------------------- phase 4: forms */
.embed-code {
  margin: 0; padding: var(--s3) var(--s4); border-radius: var(--r);
  background: var(--surface-3); border: 1px solid var(--border);
  font-family: var(--mono); font-size: 12px; line-height: 1.6;
  white-space: pre-wrap; word-break: break-all; user-select: all;
}
code { font-family: var(--mono); font-size: 12px; background: var(--surface-3); padding: 1px 5px; border-radius: 4px; }

/* --------------------------------------------------- phase 5: calendars */
.hours { display: flex; flex-direction: column; gap: var(--s2); }
.hour-row { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.hour-row .check { min-width: 108px; }
.hour-row input[type="time"] { font: inherit; font-size: 13px; padding: 5px var(--s2); border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface); color: var(--text); }

/* ------------------------------------------------- phase 8: reporting */
.bar-row { display: flex; align-items: center; gap: var(--s3); padding: var(--s2) 0; font-size: 13.5px; }
.bar-label { min-width: 130px; color: var(--text-2); }
.bar-track { flex: 1; height: 8px; border-radius: 4px; background: var(--surface-3); overflow: hidden; }
.bar-fill { display: block; height: 100%; border-radius: 4px; background: var(--accent); }
.bar-row b { min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; }

.spark { display: flex; align-items: flex-end; gap: 3px; height: 90px; }
.spark-day { flex: 1; display: flex; align-items: flex-end; gap: 1px; height: 100%; min-width: 4px; }
.spark-in, .spark-out { flex: 1; border-radius: 2px 2px 0 0; min-height: 2px; }
.spark-in { background: var(--info); }
.spark-out { background: var(--accent); }

/* Business hours editor — one row per day, so the shape is obvious at a glance. */
.hours-grid { display: flex; flex-direction: column; gap: var(--s2); }
.hours-row {
  display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: var(--s2);
}
.hours-row .check { margin: 0; }
.hours-row input[type="time"] {
  padding: 5px var(--s2); border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text);
}
@media (max-width: 760px) {
  .hours-row { grid-template-columns: 1fr; gap: 4px; }
}

/* ------------------------------------------------- the social planner grid
 *
 * A month at a glance. The cells are a fixed minimum height rather than
 * growing with their contents, so the weeks stay aligned and the shape of a
 * month — where the gaps are — is readable at a glance. That shape is the
 * reason to have a calendar rather than a list.
 */
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r2);
  overflow: hidden;
}
.cal-head {
  background: var(--surface-2);
  padding: var(--s2);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-2);
  text-align: center;
}
.cal-cell {
  background: var(--surface);
  min-height: 7.5rem;
  padding: var(--s2);
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.cal-pad { background: var(--surface-2); }
.cal-today { box-shadow: inset 0 0 0 2px var(--accent); }
.cal-day { font-size: 0.78rem; color: var(--text-2); font-weight: 600; }
.cal-today .cal-day { color: var(--accent); }

.cal-chip {
  display: block;
  width: 100%;
  text-align: left;
  border: 1px solid var(--line);
  border-left: 3px solid var(--text-2);
  border-radius: 6px;
  background: var(--surface-2);
  padding: 4px 6px;
  font: inherit;
  font-size: 0.72rem;
  line-height: 1.25;
  color: var(--text);
  cursor: pointer;
}
.cal-chip:hover:not(:disabled) { background: var(--surface-3, var(--surface-2)); border-color: var(--accent); }
.cal-chip:disabled { cursor: default; opacity: .75; }
.cal-chip.ok     { border-left-color: var(--ok); }
.cal-chip.warn   { border-left-color: var(--warn); }
.cal-chip.danger { border-left-color: var(--danger); }
.cal-chip-wide { width: auto; max-width: 22rem; }

.cal-time { display: block; font-weight: 600; color: var(--text-2); font-size: 0.68rem; }
.cal-text { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-nets { display: block; color: var(--text-2); font-size: 0.66rem; margin-top: 1px; }

/* A month of 7 columns cannot survive a phone. Below 40rem the grid becomes a
   single column of days that actually have something on them — scrolling past
   21 empty cells to find the one post is worse than no calendar at all. */
@media (max-width: 40rem) {
  .cal-grid { grid-template-columns: 1fr; }
  .cal-head { display: none; }
  .cal-cell { min-height: 0; flex-direction: row; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }
  .cal-cell:not(:has(.cal-chip)) { display: none; }
  .cal-day { min-width: 2rem; }
  .cal-chip { width: auto; flex: 1 1 12rem; }
}

/* The composer's uploaded images. */
.media-strip { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s2); }
.media-thumb { position: relative; width: 84px; height: 84px; border-radius: var(--r-sm); overflow: hidden; border: 1px solid var(--border); }
.media-thumb img, .media-thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }
.media-drop {
  border: 1.5px dashed var(--border); border-radius: var(--r); padding: var(--s3);
  text-align: center; color: var(--text-2); font-size: 0.85rem; cursor: pointer;
}
.media-drop:hover, .media-drop.on { border-color: var(--accent); color: var(--text); }

/* Dragging a post to another day. */
.cal-chip[draggable="true"] { cursor: grab; }
.cal-chip.dragging { opacity: .4; cursor: grabbing; }
.cal-cell.cal-over { background: var(--accent-soft, var(--surface-2)); box-shadow: inset 0 0 0 2px var(--accent); }

/* ------------------------------------------------------------------ toast
   A message that says its piece and leaves. It sits above the mobile tab bar
   rather than under it, and above the sheet, because the thing it most often
   has to report is why the sheet's Save did nothing. */
#toast {
  position: fixed; left: 50%; z-index: 200;
  bottom: calc(var(--s4) + var(--tabbar-h, 0px));
  max-width: min(32rem, calc(100vw - var(--s6)));
  padding: var(--s3) var(--s4);
  background: var(--text); color: var(--canvas);
  border-radius: var(--r); box-shadow: var(--shadow-3);
  font-size: 14px; line-height: 1.45; text-align: left;
  transform: translate(-50%, calc(100% + var(--s8)));
  opacity: 0; pointer-events: none;
  transition: transform .22s var(--ease, ease), opacity .22s var(--ease, ease);
}
#toast.show { transform: translate(-50%, 0); opacity: 1; }
@media (prefers-reduced-motion: reduce) { #toast { transition: opacity .01s; } }

/* ------------------------------------------------------- labelled fields
   .field-grid was used in ten panels — the automation editor, calendars,
   forms, phone numbers, business settings — and had NO rule at all. An
   unstyled <input> is about twenty characters wide and does not fill its
   parent, so every one of those fields was a stub of a box with its contents
   scrolling sideways inside it, and `grid-column: 1/-1` on the full-width
   ones did nothing because there was no grid to span. */
.field-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--s3) var(--s4);
}
.field-grid > label {
  display: flex; flex-direction: column; gap: var(--s1); min-width: 0;
  font-size: 12.5px; font-weight: 600; color: var(--text-2);
}
.field-grid > label > input,
.field-grid > label > select,
.field-grid > label > textarea {
  width: 100%; min-width: 0; padding: 9px var(--s3);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text);
  font: inherit; font-size: 14px; font-weight: 400;
}
.field-grid > label > textarea {
  /* Wraps rather than scrolling sideways, and grows to fit — the whole point
     is being able to read a long name while you are editing it. */
  /* overflow-y:auto rather than hidden: grow() sizes these to fit so no
     scrollbar appears, but if the script ever fails to run, the worst case is
     a field you can scroll — not one that silently hides its own contents. */
  resize: vertical; line-height: 1.45; overflow-y: auto; min-height: 2.6em;
}
.field-grid > label > :focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* One column on a phone: two 220px tracks do not fit, and a field you have to
   scroll inside is the thing being fixed here. */
@media (max-width: 40rem) {
  .field-grid { grid-template-columns: 1fr; }
}

/* --------------------------------------------------- the step palette
   Thirteen buttons sat permanently under the step list, so the longest thing
   on the screen was the list of things you had not done. They live behind one
   button now and close again as soon as one is chosen. */
.step-add { margin-top: var(--s3); }
.step-palette {
  margin-top: var(--s3); padding: var(--s3);
  border: 1px solid var(--border); border-radius: var(--r); background: var(--surface-2);
  display: flex; gap: var(--s2); flex-wrap: wrap;
}
/* The trigger and its filter are one sentence, so they wrap as one row
   rather than the filter hanging off a margin below the select. */
.trigger-row { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; }
.trigger-row select, .trigger-row input { min-width: 0; flex: 1 1 11rem; }

/* Who the whole automation is about, stated once under the trigger. */
.runs-for {
  margin: var(--s3) 0 0; padding: var(--s2) var(--s3);
  border-radius: var(--r-sm); background: var(--accent-soft); color: var(--text);
  font-size: 12.5px;
}

/* What a step actually acts on, under its name. */
.step-row .step-who { font-size: 12px; color: var(--text-2); margin-top: 2px; }

/* ------------------------------------------------ pipeline + stage pairs */
.stage-pair {
  margin-top: var(--s3); padding: var(--s3);
  border: 1px solid var(--border); border-radius: var(--r); background: var(--surface-2);
}
.stage-pair + .stage-pair { margin-top: var(--s2); }
.stage-pair .btn.ghost { margin-top: var(--s2); color: var(--danger-text); }

/* Reorder and remove, stacked so they do not crowd a step's contents. */
.step-tools { flex: none; display: flex; flex-direction: column; gap: 2px; }
.step-tools .icon-btn:disabled { opacity: .28; cursor: default; }

/* How long to wait: a number and its unit, side by side. */
.wait-row { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
.wait-row input { width: 5.5rem; }
.wait-row input[type="time"] { width: auto; }

/* When the step after a wait may go out — a hold, not a delay. */
.send-window { display: grid; gap: var(--s2); margin-top: var(--s2); padding-top: var(--s2); border-top: 1px dashed var(--border); }
.send-window .chips { gap: var(--s3); }

/* Controls inside a step sit tighter than a panel's. */
.step-row .field-grid { margin: 0; }
.step-row input.mini + .ta { margin-top: var(--s2); }

/* The merge fields, under the messages that use them. */
.merge-hint { margin: var(--s3) 0 0; line-height: 1.8; }
.merge-hint code { font-size: 11.5px; color: var(--text); white-space: nowrap; }

/* Booking for a customer: the open times, as buttons. */
.slot-grid { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s2); }
.slot-grid .btn { min-width: 5.5rem; justify-content: center; }
.pick-list { margin-top: var(--s2); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; }
.pick-list button {
  display: block; width: 100%; text-align: left; padding: var(--s2) var(--s3);
  border: 0; border-bottom: 1px solid var(--border); background: var(--surface); color: var(--text); font: inherit; font-size: 13.5px; cursor: pointer;
}
.pick-list button:last-child { border-bottom: 0; }
.pick-list button:hover { background: var(--surface-2); }

/* ------------------------------------ what a client sees in their portal
   Read-only chips: the tabs they have, and the recommendations currently on
   their dashboard. A pinned one is marked because it was written for them
   rather than matched by a rule. */
/* .chips is already defined above; only the chip itself is new. */
.chip {
  display: inline-block; padding: 3px var(--s3); border-radius: var(--r-full);
  background: var(--surface-3); border: 1px solid var(--border);
  font-size: 12.5px; color: var(--text-2); white-space: nowrap;
}
.chip.on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--text); font-weight: 600; }

/* One-line add / edit forms in settings tables (Fields & values). */
.inline-form { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; }
.inline-form input, .inline-form select {
  flex: 1 1 160px; min-width: 0;
  font: inherit; font-size: 13px; padding: 6px var(--s3);
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface); color: var(--text);
}
.inline-form .btn { flex: 0 0 auto; }
/* Used by nine delete / unpublish buttons that had no style of their own. */
.btn.danger { color: var(--danger-text); border-color: color-mix(in srgb, var(--danger) 35%, transparent); }
.btn.danger:hover { background: var(--danger-soft); }
/* A link styled as a button reads as a button, not as underlined text. */
a.btn { text-decoration: none; display: inline-flex; align-items: center; }


/* ------------------------------------------------------------- the dialer
 *
 * On a phone it is a bar pinned under the header — Franklin, 5 Oct 2026:
 * "lets make it a widget at the top of the screen on mobile" — because that
 * is where somebody reaches for it between jobs. On a desktop the bar is out
 * of the way and the same panel drops from the phone button in the header.
 *
 * Not a softphone: the server rings the staff member's own phone and bridges
 * the customer, so nothing here needs a microphone or a permission prompt.
 */
.dialbar { display: none; }
.dial-open {
  display: flex; align-items: center; gap: var(--s2);
  width: 100%; padding: var(--s2) var(--s3);
  background: var(--surface-2); color: var(--text);
  border: 0; border-bottom: 1px solid var(--line);
  font: inherit; cursor: pointer;
}
.dial-open svg { width: 1.1em; height: 1.1em; }
/* A bubble coming out of the phone button, so it reads as something extra
   sitting over the page and not as part of it (Franklin, 9 Nov 2026).
   aimDialBubble() sets --tail-x. */
.dial-panel {
  --bubble-line: var(--accent); --bubble-fill: var(--surface);
  position: relative; padding: var(--s3) var(--s4) var(--s4);
  background: var(--bubble-fill); border: 2px solid var(--bubble-line); border-radius: 24px;
  box-shadow: 0 16px 44px rgb(0 0 0 / .34);
}
.dial-tail { position: absolute; top: -21px; left: calc(var(--tail-x, 80%) - 20px); width: 36px; height: 22px; overflow: visible; }
.dial-tail .fill { fill: var(--bubble-fill); }
.dial-tail .line { fill: none; stroke: var(--bubble-line); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.dial-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--s2); }
.dial-row { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
.dial-field { position: relative; flex: 1 1 10rem; min-width: 0; }
.dial-field:has(.dial-clear) input { padding-right: 42px; text-overflow: ellipsis; }
.dial-clear {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  display: grid; place-items: center; width: 34px; height: 34px; padding: 0;
  border: 0; border-radius: 50%; background: none; color: var(--text-2); cursor: pointer;
}
.dial-clear:hover { background: var(--surface-2); color: var(--text); }
.dial-clear svg { width: 16px; height: 16px; }
/* A tel input sizes to its content and will not shrink inside a flex row
   without this, which pushed Call off the edge on a narrow phone. */
.dial-row input {
  width: 100%; min-width: 0; padding: 9px var(--s3);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text); font: inherit; font-size: 16px;   /* 16px: iOS zooms the page on anything smaller */
  -webkit-appearance: none; appearance: none;
}
.dial-row input:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: transparent; }
.dial-hits { list-style: none; margin: var(--s2) 0 0; padding: 0; }
.dial-hits button {
  display: flex; justify-content: space-between; gap: var(--s3); width: 100%;
  padding: var(--s2); background: none; border: 0; border-radius: var(--r1);
  color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.dial-hits button:hover, .dial-hits button:focus-visible { background: var(--surface-2); }
.dial-hits span { color: var(--muted); }
.dial-note { margin: var(--s2) 0 0; color: var(--muted); font-size: .85rem; text-align: left; }
.dial-note.bad { color: var(--bad, #b3261e); }

/* Desktop: the panel drops out of the header button, over the page. */
@media (min-width: 761px) {
  .dialbar:has(.dial-panel) { display: block; position: absolute; right: var(--s4); top: calc(var(--topbar-h) + 8px); z-index: 60; width: min(28rem, calc(100vw - 2rem)); }
}

/* Phone: the same phone button in the header as on a desktop (Franklin,
   5 Oct 2026 — the always-on "Call someone" bar cost a row of screen on
   every page). Open, the panel drops full-width under the header. */
@media (max-width: 760px) {
  .dialbar { display: none; }
  .dialbar:has(.dial-panel) { display: block; position: fixed; left: var(--s3); right: var(--s3); top: calc(var(--topbar-h, 56px) + 8px); z-index: 60; }
  /* Search box on its own line, the buttons under it on one line. */
  .dial-field { flex: 1 1 100%; }
  .dial-row #dial-go { flex: 1; justify-content: center; }
}

/* The three per-person actions wrap rather than stretching the row on a phone. */
.people-acts { display: flex; gap: var(--s2); flex-wrap: wrap; }

/* Contracts: picking who a contract is for. */
button.doc-who-row {
  width: 100%; border: 0; border-bottom: 1px solid var(--border); background: none;
  font: inherit; color: inherit; text-align: left; cursor: pointer; border-radius: 0;
}
button.doc-who-row:hover { background: var(--surface-2); }

/* An email in a thread reads as an email: labelled, subject first, long
   bodies folded so one auto-reply does not fill the screen. */
.msg.email .bubble.email-bubble { max-width: min(560px, 92%); white-space: normal; }
.msg.email.out .bubble.email-bubble { background: var(--surface); color: var(--text); border: 1px solid var(--border-strong); }
.email-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 6px; }
.email-head svg { width: 15px; height: 15px; color: var(--text-3); }
.email-tag, .chan-tag {
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 1px 6px; border-radius: 999px; background: var(--info-soft); color: var(--info-text);
}
.email-subject { flex-basis: 100%; font-size: 14px; }
.email-full { white-space: pre-wrap; overflow-wrap: anywhere; font-size: calc(13.5px * var(--z, 1)); line-height: 1.55; }
.email-body summary { list-style: none; cursor: pointer; }
.email-body summary::-webkit-details-marker { display: none; }
.email-preview { display: block; white-space: pre-wrap; overflow-wrap: anywhere; font-size: calc(13.5px * var(--z, 1)); line-height: 1.55; color: var(--text-2); }
.email-more { display: inline-block; margin-top: 6px; font-size: 12.5px; font-weight: 650; color: var(--accent); }
.email-body[open] summary { display: none; }

/* Dialer: what will happen, and the two things you can change about it. */
.link-inline { border: 0; background: none; padding: 0; margin-left: 4px; font: inherit; color: var(--accent); font-weight: 600; cursor: pointer; text-decoration: underline; }
.dial-opts { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: var(--s2) var(--s3); margin-top: var(--s2); }
.dial-opts label { display: flex; flex-direction: column; gap: 4px; font-size: 12.5px; font-weight: 600; color: var(--text-2); }
.dial-opts select, .dial-opts input { width: 100%; min-width: 0; padding: 8px var(--s3); border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface); color: var(--text); font: inherit; font-size: 14px; font-weight: 400; }
#dial-keypad { min-width: 2.75rem; font-weight: 700; font-size: 12px; }
#dial-keypad.on { background: var(--accent-soft); }
#dial-to::-webkit-search-cancel-button { display: none; }
#dial-opts { margin: 0; color: var(--text-3); font-weight: 500; font-size: 12.5px; text-decoration: none; }

/* Message zoom: pinch on a phone, or the Aa button. */
.zoom-btn { font-weight: 700; font-size: 12.5px; min-width: 2.25rem; }
.zoom-pop { position: absolute; right: var(--s3); top: calc(100% + 4px); z-index: 20; display: flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s3); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); box-shadow: var(--shadow-2); font-size: 12.5px; }
.zoom-pop input { width: 8rem; accent-color: var(--accent); }
.thread-head { position: relative; }
.zoom-toast { position: absolute; left: 50%; top: 84px; transform: translateX(-50%); z-index: 5; pointer-events: none;
  padding: 4px 10px; border-radius: 999px; background: rgba(24,15,47,.82); color: #fff; font-size: 12px; font-weight: 600; }
.thread-view { position: relative; }
/* The name and number in a thread header stay on one line each. */
.thread-head > div[style*="min-width:0"] { flex: 1 1 auto; overflow: hidden; }
.thread-head > div[style*="min-width:0"] > div { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Where an outgoing message came from: an automation, or outside software. */
.src-tag { display: inline-flex; align-items: center; gap: 3px; padding: 0 6px; border-radius: 999px;
  font-size: 10.5px; font-weight: 650; background: var(--surface-3); color: var(--text-2); }
.src-tag.ext { background: var(--accent-soft); color: var(--text); }
.src-tag svg { width: 11px; height: 11px; }

/* A call in progress from this device. */
.dial-panel.in-call { display: flex; flex-direction: column; gap: var(--s3); }
.call-who { display: flex; align-items: center; gap: var(--s2); font-size: 15px; }
.call-who b { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.call-state { font-variant-numeric: tabular-nums; color: var(--text-2); font-size: 13.5px; }
.call-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 0 4px var(--ok-soft); animation: call-pulse 1.6s ease-in-out infinite; }
@keyframes call-pulse { 50% { box-shadow: 0 0 0 7px transparent; } }
@media (prefers-reduced-motion: reduce) { .call-dot { animation: none; } }
.call-acts { display: flex; gap: var(--s2); }
.call-acts .btn { flex: 1; justify-content: center; }
.call-acts .btn.on { background: var(--accent-soft); }
.btn.danger#call-hangup { background: var(--danger); color: #fff; border-color: transparent; }
.call-pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s2); }
.call-pad .btn { justify-content: center; font-size: 18px; font-weight: 650; padding: 10px 0; }
/* The header phone button while a call is live, so it can be found again. */
#dial-btn.on { background: var(--accent); color: var(--accent-ink); }
#dial-btn.live { background: var(--ok); color: #fff; }

/* "A new version is ready" — only when reloading on its own would lose something. */
.update-bar { position: fixed; left: 50%; bottom: calc(var(--tabbar-h, 0px) + var(--s4)); transform: translateX(-50%); z-index: 90;
  display: flex; align-items: center; gap: var(--s3); padding: var(--s2) var(--s3) var(--s2) var(--s4);
  background: var(--rail); color: #fff; border-radius: 999px; box-shadow: var(--shadow-3); font-size: 13.5px; white-space: nowrap; }

/* Contracts: the formatted editor (Word-style). 12pt is the starting size
   everywhere a contract appears, so what is written is what is signed. */
.rich { border: 1px solid var(--border); border-radius: var(--r); background: var(--surface); margin-top: var(--s1); }
.rich-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 6px; border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 2; background: var(--surface); border-radius: var(--r) var(--r) 0 0; }
.rich-tools .btn { min-width: 2.1rem; justify-content: center; }
.rich-tools .btn.on { background: var(--accent-soft); border-color: var(--accent-line); }
.rich-size { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-2); }
.rich-size input { width: 4.2rem; padding: 5px 6px; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface); color: var(--text); font: inherit; font-size: 14px; }
.rich-sep { width: 1px; height: 20px; background: var(--border); margin: 0 2px; }
.rich-doc { min-height: 320px; max-height: 70vh; overflow-y: auto; padding: var(--s4) var(--s5); outline: none; overflow-wrap: anywhere; }
.rich-doc:empty::before { content: attr(data-placeholder); color: var(--text-3); }
.doc-v2 { font-size: 12pt; line-height: 1.55; }
.doc-v2 ul, .doc-v2 ol { margin: 4px 0; padding-left: 1.4em; }
tr.row-link { cursor: pointer; }
tr.row-link:hover td { background: var(--surface-2); }
.dial-panel.incoming { --bubble-line: var(--ok); }
.call-dot.ring { background: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft); }
.btn.answer { background: var(--ok); color: #fff; border-color: transparent; }
.dial-opts .check-row { flex-direction: row; align-items: center; gap: var(--s2); grid-column: 1 / -1; font-weight: 500; color: var(--text); }

/* Contracts: who signs, and how far they have got. */
.signers { margin-top: var(--s3); padding: var(--s3); border: 1px solid var(--border); border-radius: var(--r); background: var(--surface-2); display: flex; flex-direction: column; gap: var(--s3); }
.signers-top, .signers-order { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); }
.signer-card { padding: var(--s3); border: 1px solid var(--border); border-radius: var(--r); background: var(--surface); }
.signer-head { display: flex; align-items: baseline; gap: var(--s2); margin-bottom: var(--s2); }
.signer-lines { display: flex; flex-direction: column; gap: 4px; margin: 4px 0 var(--s2) var(--s2); }
.signer-line { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); font-size: 13px; }

.doc-new-person { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--border); }

/* Contracts: choosing which one — a list you can always change. */
.step-title { font-weight: 700; font-size: 14px; margin: 0 0 var(--s2); }
/* Contract lists that fold and search. */
.tpl-fold { display: inline-flex; align-items: center; gap: 4px; border: 0; background: none; padding: 2px 6px; border-radius: 999px; cursor: pointer; color: var(--text-2); }
.tpl-fold:hover { background: var(--surface-2, rgba(127,127,127,.12)); }
.tpl-chev { width: 16px; height: 16px; stroke-width: 2; transform: rotate(-90deg); transition: transform .15s; }
.tpl-chev.open { transform: none; }
.tpl-title, .tpl-step { cursor: pointer; user-select: none; }
.tpl-step { display: flex; align-items: center; gap: 4px; }
.tpl-search-pad { padding-bottom: 0; }
.tpl-search { margin: 0 0 var(--s2); }
.contract-choice [hidden], .tbl tr[hidden], .tpl-none[hidden] { display: none !important; }
.contract-choice { display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--s3); }
.contract-option { display: flex; align-items: flex-start; gap: var(--s3); width: 100%; text-align: left; padding: var(--s2) var(--s3);
  border: 1px solid var(--border); border-radius: var(--r); background: var(--surface); color: var(--text); font: inherit; cursor: pointer; }
.contract-option:hover { border-color: var(--border-strong); }
.contract-option.on { border-color: var(--accent); background: var(--accent-soft); }
.contract-option .co-text { display: flex; flex-direction: column; min-width: 0; }
.contract-option .co-text .cell-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.radio-dot { flex: none; width: 16px; height: 16px; margin-top: 2px; border-radius: 50%; border: 2px solid var(--border-strong); }
.contract-option.on .radio-dot { border-color: var(--accent); box-shadow: inset 0 0 0 3px var(--surface); background: var(--accent); }

/* A number's settings, read-only for staff: same look as a card. */
fieldset.number-card { min-width: 0; margin: 0 0 var(--s4); }
fieldset.number-card[disabled] input, fieldset.number-card[disabled] textarea, fieldset.number-card[disabled] select { opacity: .75; }

/* A2P registration */
.a2p-lead { margin: 0 0 var(--s3); }
.tbl.kv th { text-align: left; font-weight: 600; color: var(--text-2); width: 13rem; vertical-align: top; }
.tbl.kv td { white-space: normal; overflow-wrap: anywhere; }
.a2p-text h4 { margin: var(--s4) 0 var(--s1); font-size: 0.9rem; }
.a2p-text p, .a2p-text li { line-height: 1.55; }
.a2p-text ul { margin: 0; padding-left: 1.2rem; display: grid; gap: var(--s2); }

/* ---- Social area: AI box, previews, media library, analytics, approvals ---- */
.ai-box { border: 1px dashed var(--border); border-radius: var(--r-sm); padding: var(--s3); margin: 0 0 var(--s3); background: var(--surface-2); }
.pill .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
.media-thumb.pick { padding: 0; cursor: pointer; background: none; }
.media-thumb.pick:hover { border-color: var(--accent); }
.preview-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--s3); margin-top: var(--s3); }
.preview-card { border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface); overflow: hidden; }
.preview-head { display: flex; justify-content: space-between; gap: var(--s2); padding: var(--s2) var(--s3); font-size: 13px; }
.preview-card img, .preview-card video { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; background: var(--surface-3); }
.preview-body { padding: var(--s2) var(--s3); margin: 0; font-size: 13px; white-space: pre-wrap; overflow-wrap: anywhere; }
.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--s3); }
.media-tile { border: 1px solid var(--border); border-radius: var(--r-sm); padding: var(--s2); background: var(--surface); }
.media-tile img, .media-tile video { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 6px; display: block; margin-bottom: var(--s2); }
.day-bars { display: flex; align-items: flex-end; gap: 2px; height: 120px; }
.day-bar { flex: 1; background: var(--accent); border-radius: 2px 2px 0 0; min-width: 2px; opacity: .85; }
.stat .d .up { color: var(--ok-text); } .stat .d .down { color: var(--danger-text); }
.pick-list { display: flex; flex-direction: column; gap: var(--s2); max-height: 22rem; overflow: auto; }
.pick-row { display: flex; gap: var(--s2); align-items: flex-start; font-size: 13.5px; }
.pick-row input { margin-top: 3px; }
.report-title { display: none; }

/* Analytics prints as a client report: just the numbers, no app chrome. */
@media print {
  .topbar, .iconrail, .panel-nav, .tabbar, .dialbar, .page-head .seg, .page-head select, .page-head button, #an-print { display: none !important; }
  .shell, .content, #content { display: block !important; margin: 0 !important; padding: 0 !important; overflow: visible !important; height: auto !important; }
  .report-title { display: block; margin: 0 0 16px; }
  .report-title h2 { margin: 0; font-size: 20px; }
  .card, .stat { break-inside: avoid; box-shadow: none; }
  body { background: #fff; }
}

/* An empty day says it can be clicked. */
.cal-cell[data-drop-day] { cursor: pointer; position: relative; }
.cal-cell[data-drop-day]:hover { background: var(--surface-2); }
.cal-cell[data-drop-day]:hover::after { content: '+ Post'; position: absolute; right: 6px; top: 4px; font-size: 0.68rem; color: var(--accent); font-weight: 600; }
.media-drop { padding: var(--s4) var(--s3); }

/* Accounts: one tile per network. */
.net-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: var(--s3); }
.net-tile { border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface); padding: var(--s3) var(--s4); display: flex; flex-direction: column; gap: var(--s2); align-items: flex-start; }
.net-tile.on { border-color: var(--ok); }
.net-name { font-weight: 700; }
.net-acct { display: flex; align-items: center; gap: 6px; width: 100%; font-size: 13px; }
.net-who { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.linkish { background: none; border: 0; padding: 0; color: var(--text-3); font: inherit; font-size: 12px; cursor: pointer; text-decoration: underline; }

/* ---- Liquid glass switch (Social pages) ------------------------------------
   The same three layers as the portal's Overview bar (salt-portal
   css/portal.css .promo): a drifting violet/amber field bent by the SVG
   filter #glass-liquid, a tinted blurred glass over it with a bright top rim
   and a pointer-following highlight, and the buttons on top. The bar carries
   its own world, so it reads the same on the light and dark themes. */
.glass-switch { position: relative; isolation: isolate; overflow: hidden;
  display: flex; gap: 4px; padding: 5px; border-radius: 999px;
  margin: 0; width: fit-content; max-width: 100%; min-width: 0;
  box-shadow: 0 12px 30px rgba(24, 15, 47, 0.25), 0 2px 6px rgba(24, 15, 47, 0.12);
  --gx: 18%; --gy: 0%; }
.glass-switch::before { content: ""; position: absolute; inset: -40px; z-index: -2; pointer-events: none;
  background:
    radial-gradient(38% 120% at 12% 30%, rgba(233, 161, 59, 0.75), transparent 70%),
    radial-gradient(34% 140% at 58% 90%, rgba(122, 82, 214, 0.85), transparent 72%),
    radial-gradient(30% 120% at 92% 20%, rgba(240, 176, 85, 0.55), transparent 70%),
    linear-gradient(100deg, #2b1d52, #180f2f 55%, #3a2670);
  background-size: 160% 160%, 150% 150%, 170% 170%, 100% 100%;
  filter: url(#glass-liquid); will-change: transform;
  animation: glass-drift 18s ease-in-out infinite alternate; }
.glass-switch::after { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; border-radius: inherit;
  background:
    radial-gradient(140px 70px at var(--gx) var(--gy), rgba(255, 255, 255, 0.22), transparent 70%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0) 45%),
    rgba(24, 15, 47, 0.52);
  -webkit-backdrop-filter: blur(7px) saturate(170%); backdrop-filter: blur(7px) saturate(170%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45), inset 0 0 0 1px rgba(255, 255, 255, 0.12),
    inset 0 -1px 0 rgba(255, 255, 255, 0.10), inset 0 -14px 24px -18px rgba(240, 176, 85, 0.35); }
/* Drift by transform, not background-position: the rippled field is drawn
   once and slid by the GPU, instead of re-running the SVG filter on every
   frame. */
@keyframes glass-drift {
  0%   { transform: translate3d(-18px, -6px, 0); }
  50%  { transform: translate3d(10px, 8px, 0); }
  100% { transform: translate3d(22px, -4px, 0); } }
@media (prefers-reduced-motion: reduce) { .glass-switch::before { animation: none; } }
.glass-switch button { position: relative; border: 1px solid transparent; background: transparent; color: rgba(255, 255, 255, 0.82);
  font: inherit; font-size: 13px; font-weight: 600; padding: 6px 14px; border-radius: 999px; cursor: pointer;
  text-shadow: 0 1px 2px rgba(14, 8, 31, 0.45); transition: background .15s, color .15s, transform .15s; }
.glass-switch button:hover { color: #fff; background: rgba(255, 255, 255, 0.08); }
.glass-switch button.on { color: #fff; border-color: rgba(255, 255, 255, 0.30);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.24), rgba(255, 255, 255, 0.07));
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.50), 0 4px 12px rgba(14, 8, 31, 0.25); }
.glass-switch button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.glass-switch.small { padding: 4px; margin: 0; }
.glass-switch.small button { padding: 4px 12px; font-size: 12.5px; }



/* ---- Tabs on top, no side list, liquid glass everywhere -------------------
   Franklin, 8 Oct 2026: the glass tabs replace the side list in every
   section, and the whole app takes the liquid glass look — switchable off
   from the sparkle in the top bar (per browser). */
.shell.tabs-nav, .shell.tabs-nav.panel-collapsed { grid-template-columns: var(--iconrail-w) 1fr; }
.shell.tabs-nav .panel-nav { display: none; }
.shell.tabs-nav .content { border-top-left-radius: var(--frame-r); }
#panel-show, #panel-toggle { display: none !important; }
/* The inbox's own Unread/All/Mine/Starred row repeated the tabs above it. */
.shell.tabs-nav .thread-list-head:has(> #inbox-filter:only-child) { display: none; }

.area-top { display: flex; align-items: center; gap: var(--s3); margin: 0 0 var(--s4); min-width: 0; }
/* The inbox fills the screen edge to edge; its tabs sit in a strip above it. */
.content.flush { flex-direction: column; }
.content.flush > .area-top { flex: none; margin: 0; padding: var(--s3) var(--s4); }
.gs-track { display: flex; gap: 4px; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; min-width: 0; }
.gs-track::-webkit-scrollbar { display: none; }
.gs-track button { white-space: nowrap; flex: none; }
.gs-count { display: inline-block; min-width: 18px; padding: 0 5px; margin-left: 4px; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink); font-size: 10.5px; font-weight: 800; line-height: 17px; text-shadow: none; }

/* Glass off: the same tabs as a plain segmented control. */
.glass-switch.plain { box-shadow: none; background: var(--surface-3); }
.glass-switch.plain::before, .glass-switch.plain::after { display: none; }
.glass-switch.plain button { color: var(--text-2); text-shadow: none; }
.glass-switch.plain button:hover { color: var(--text); background: var(--surface-2); }
.glass-switch.plain button.on { color: var(--text); background: var(--surface); border-color: var(--border);
  box-shadow: var(--shadow-1); -webkit-backdrop-filter: none; backdrop-filter: none; }

/* The sparkle in the top bar: lit when glass is on. */
.glass-btn.on { color: #f3c37a; background: rgba(233, 161, 59, 0.16); }

/* The glass theme. The page carries its own world — the violet and amber
   light of the portal bar, fixed behind everything and drifting slowly —
   and every surface on it becomes tinted, blurred glass with a bright rim.
   Done by re-pointing the theme tokens on <body>, so every card, button,
   field, table, drawer and menu in the app follows without its own rule. */
body.glass {
  --surface: rgba(255, 255, 255, 0.07);
  --surface-2: rgba(255, 255, 255, 0.05);
  --surface-3: rgba(255, 255, 255, 0.11);
  --canvas: transparent;
  --border: rgba(255, 255, 255, 0.14);
  --border-strong: rgba(255, 255, 255, 0.24);
  --text: #f6f3ff; --text-2: rgba(246, 243, 255, 0.78); --text-3: rgba(246, 243, 255, 0.60);
  --shadow-1: 0 8px 24px rgba(14, 8, 31, 0.28);
  --glass-solid: rgba(30, 20, 60, 0.82);
  /* Glass is always light text on a dark field, whatever the light/dark
     setting — so the tinted highlights (notes, badges, warnings) take their
     dark-theme values too, or a light-mode note is white on cream. */
  --accent-soft: rgba(233, 161, 59, 0.18); --accent-line: rgba(233, 161, 59, 0.45);
  --ok-soft: rgba(47, 143, 91, 0.24); --warn-soft: rgba(233, 161, 59, 0.20);
  --danger-soft: rgba(192, 57, 43, 0.26); --info-soft: rgba(59, 111, 212, 0.26);
  /* Lighter still: the glass field runs from deep violet to an amber glow. */
  --ok-text: #8fe8b4; --warn-text: #fbd49a; --danger-text: #ffbab0; --info-text: #b3caff;
  --shadow-2: 0 4px 16px rgba(0,0,0,.45); --shadow-3: 0 16px 40px rgba(0,0,0,.55);
  color: var(--text);
  background: #180f2f;
}
/* The field lives on its own fixed layer and drifts by transform only.
   It used to be the body's background with background-attachment: fixed,
   animated by background-position — that made the browser repaint the
   whole window on every frame of a scroll, and on a long inbox it fell
   behind and showed blank space until it caught up (Franklin, 8 Oct 2026).
   A transformed layer is moved by the GPU and never repainted. */
body.glass::before {
  content: ''; position: fixed; inset: -12%; z-index: -1; pointer-events: none;
  background:
    radial-gradient(45% 60% at 10% 14%, rgba(233, 161, 59, 0.38), transparent 70%),
    radial-gradient(40% 70% at 68% 84%, rgba(122, 82, 214, 0.55), transparent 72%),
    radial-gradient(35% 50% at 92% 12%, rgba(240, 176, 85, 0.28), transparent 70%),
    linear-gradient(120deg, #2b1d52, #180f2f 55%, #3a2670);
  /* The frame's tone: dimmer than the work area laid over it. */
  box-shadow: inset 0 0 0 100vmax rgba(16, 10, 34, 0.22);
  will-change: transform;
  animation: glass-field-drift 40s ease-in-out infinite alternate;
}
@keyframes glass-field-drift {
  0%   { transform: translate3d(-4%, -3%, 0) scale(1); }
  100% { transform: translate3d(4%, 3%, 0) scale(1.06); } }
@media (prefers-reduced-motion: reduce) { body.glass::before { animation: none; } }

/* The work area is a pane of glass on the field. The top bar and the rail
   are ONE frame, as they are without glass: the same tint and blur on both
   and no line between them, so the corner where they meet reads as a single
   L (Franklin, 8 Oct 2026 — the bar had its own tint and the rail none, so
   the corner broke). */
body.glass { color-scheme: dark; }
/* The frame (top bar + rail) gets its darker tone from the field itself,
   not from tints painted on each bar: two painted bars leave the square
   behind the work area's rounded corner uncovered, and the lighter field
   showed there as a hard corner under the round one (Franklin, 8 Oct 2026).
   So the bars are clear, the whole field is dimmed, and the work area is
   the lighter pane laid on it — one shape, one corner. */
body.glass .content { background: rgba(255, 255, 255, 0.05); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12); }
body.glass :is(.topbar, .iconrail) { background: none; box-shadow: none; }
body.glass .railbtn.on { background: linear-gradient(180deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.06));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.40); }
body.glass .search { background: rgba(255, 255, 255, 0.08); border-color: rgba(255, 255, 255, 0.16); }

/* No backdrop blur on anything that scrolls. Over a soft gradient a blur
   adds almost nothing to see, and every blurred box has to be recomputed on
   every frame it moves — that, with the old full-window repaint, is what
   left blank space while scrolling. Tint and a bright rim do the work;
   real blur is kept for the things that float above the page. */
body.glass :is(.card, .stat, .net-tile, .preview-card, .media-tile, .ai-box) {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), inset 0 0 0 1px rgba(255, 255, 255, 0.06), 0 10px 30px rgba(14, 8, 31, 0.30);
}
/* Things that float over the page need to be read, so their glass is
   nearly solid: menus, the drawer, the dialer, the phone's tab bar. */
body.glass :is(.sheet, .drawer, .dial-panel, .tabbar, .update-bar, .app-dialog) {
  background: var(--glass-solid);
  -webkit-backdrop-filter: blur(22px) saturate(160%); backdrop-filter: blur(22px) saturate(160%);
  border-color: rgba(255, 255, 255, 0.16);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 18px 48px rgba(10, 6, 24, 0.45);
}
/* The outline is what separates the bubble from a page made of the same glass.
   Solid, because the tail has to cover the outline where the two join. */
body.glass .dial-panel { --bubble-fill: #1e143c; background: var(--bubble-fill); border-color: var(--bubble-line); }
body.glass #toast { background: rgba(246, 243, 255, 0.95); color: #1b1430; }
body.glass .app-dialog::backdrop { background: rgba(10, 6, 24, 0.55); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }

body.glass .btn:not(.primary):not(.danger) {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.05));
  border-color: rgba(255, 255, 255, 0.24); color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
}
body.glass .btn:not(.primary):not(.danger):hover { background: linear-gradient(180deg, rgba(255, 255, 255, 0.24), rgba(255, 255, 255, 0.09)); }
body.glass .btn.primary { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55), 0 6px 16px rgba(233, 161, 59, 0.30); }
/* Not the fields drawn inside a box that is already the field — the search
   bars, the inbox reply box, the assistant's input. Given their own tint and
   rim here, each showed as a box inside a box (Franklin, 15 Oct 2026). */
body.glass :is(.ta, input, select, textarea):not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="range"]):not(.search input, .composer .box textarea, .sa-input) {
  background: rgba(14, 8, 31, 0.30); color: var(--text); border-color: rgba(255, 255, 255, 0.16);
}
body.glass .search.wide input { color: var(--text); }
body.glass :is(input, textarea)::placeholder { color: rgba(246, 243, 255, 0.45); }
body.glass select option, body.glass select optgroup { background: #1f1640; color: #fff; }
body.glass .seg { background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.12); }
body.glass .seg button { color: var(--text-2); background: transparent; }
body.glass .seg button.on { color: #fff; background: linear-gradient(180deg, rgba(255, 255, 255, 0.24), rgba(255, 255, 255, 0.07));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45); }
body.glass .pill:not(.ok):not(.warn):not(.danger):not(.info):not(.accent) { background: rgba(255, 255, 255, 0.10); color: var(--text); }
body.glass .tbl th { color: var(--text-3); }
body.glass :is(.tbl td, .tbl th) { border-color: rgba(255, 255, 255, 0.10); }
body.glass .cal-cell { background: rgba(255, 255, 255, 0.03); }
body.glass .cal-pad { background: rgba(0, 0, 0, 0.12); }
body.glass .cal-chip { background: rgba(255, 255, 255, 0.10); }
body.glass .content a:not(.btn) { color: #f3c37a; }
body.glass .warn-note { background: rgba(233, 161, 59, 0.16); color: #f8d49a; }
body.glass .thread.on, body.glass .thread:hover { background: rgba(255, 255, 255, 0.08); }
@media print { body.glass { background: #fff; animation: none; } body.glass .content { background: #fff; } }

/* ------------------------------------------- Reports → Assistant activity
   The owner's read-back of assistant chats: two ranked lists, the chat list
   (rows open a drawer), and the chat itself drawn like the widget's — the
   person on the right, the assistant on the left, offers as cards. */
.aa-insights { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--s4); margin-bottom: var(--s5); }
.aa-insights .card { margin: 0; }
.aa-insights .card-pad { padding-top: var(--s2); padding-bottom: var(--s3); }
.aa-insights .mini-row span { min-width: 0; overflow-wrap: anywhere; }
table.tbl tr[data-aa-open] { cursor: pointer; }
table.tbl tr[data-aa-open]:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.aa-date { display: flex; align-items: center; gap: var(--s2); font-size: 13px; color: var(--text-2); }
.aa-pager { justify-content: center; margin-top: var(--s3); }
.aa-log { display: flex; flex-direction: column; gap: var(--s3); }
.aa-msg { display: flex; flex-direction: column; max-width: 86%; }
.aa-user { align-self: flex-end; align-items: flex-end; }
.aa-bot { align-self: flex-start; align-items: flex-start; }
.aa-bubble {
  padding: var(--s3) var(--s4); border-radius: 16px; font-size: 13.5px; line-height: 1.45;
  white-space: pre-wrap; overflow-wrap: anywhere; border: 1px solid var(--border);
}
.aa-user .aa-bubble { background: var(--accent-soft); border-color: var(--accent-line); border-bottom-right-radius: 6px; }
.aa-bot .aa-bubble { background: var(--surface-2); border-bottom-left-radius: 6px; }
.aa-time { font-size: 11px; color: var(--text-3); margin-top: 3px; }
.aa-act { display: flex; align-items: center; gap: var(--s2); font-size: 12.5px; color: var(--text-3); flex-wrap: wrap; }
.aa-act svg { width: 14px; height: 14px; flex: none; }
.aa-act .aa-time { margin: 0 0 0 auto; }
.aa-notice { padding: var(--s2) var(--s3); border-radius: var(--r); background: var(--warn-soft); color: var(--warn-text); }
.aa-files { margin-top: var(--s2); }
.aa-files .pill { text-decoration: none; }
.aa-offer { border: 1px solid var(--border); border-radius: var(--r); padding: var(--s3) var(--s4); background: var(--surface-2); }
.aa-offer-head { display: flex; align-items: center; gap: var(--s2); justify-content: space-between; flex-wrap: wrap; font-size: 13.5px; }
.aa-details { display: grid; grid-template-columns: minmax(70px, auto) 1fr; gap: 2px var(--s3); margin: var(--s2) 0 0; font-size: 13px; }
.aa-details dt { color: var(--text-3); }
.aa-details dd { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.aa-result { margin-top: var(--s2); font-size: 13px; }
.aa-offer .aa-time { display: block; }
body.glass .aa-bot .aa-bubble, body.glass .aa-offer { background: rgba(255, 255, 255, 0.07); border-color: rgba(255, 255, 255, 0.14); }
body.glass .aa-user .aa-bubble { background: rgba(233, 161, 59, 0.18); border-color: rgba(233, 161, 59, 0.32); }
body.glass .aa-notice { background: rgba(233, 161, 59, 0.16); color: #f8d49a; }
@media (max-width: 760px) {
  .aa-msg { max-width: 94%; }
  .aa-date { flex: 1 1 45%; }
  .aa-date input { flex: 1; min-width: 0; }
}

/* Phones: the tab row scrolls sideways with a fade at the edge; grids go
   single column; an open conversation keeps the whole screen. */
@media (max-width: 760px) {
  .shell.tabs-nav .content { border-radius: var(--frame-r) var(--frame-r) 0 0; }
  .area-top .glass-switch { flex: 1; width: auto; }
  .gs-track { -webkit-mask-image: linear-gradient(90deg, #000 82%, transparent); mask-image: linear-gradient(90deg, #000 82%, transparent); padding-right: 24px; }
  .content.flush > .area-top { padding: var(--s2) var(--s3); }
  .content.flush:has(.inbox.open-thread) > .area-top { display: none; }
  .page-head { gap: var(--s2); }
  .page-head .glass-switch.small { margin: 0 !important; }
  .net-grid, .preview-grid { grid-template-columns: 1fr; }
  .media-grid { grid-template-columns: repeat(2, 1fr); }
  .field-grid { grid-template-columns: 1fr; }
  .day-bars { height: 90px; }
}

/* ---- Scrollbars ------------------------------------------------------------
   Thin, rounded and quiet, with a clear track, so they sit inside the design
   instead of the browser's grey slab (Franklin, 8 Oct 2026). Firefox reads
   scrollbar-color/width; Chrome and Safari read the ::-webkit rules. */
:root { --sb-thumb: color-mix(in srgb, var(--text-3) 45%, transparent); --sb-thumb-hover: color-mix(in srgb, var(--text-3) 75%, transparent); }
body.glass { --sb-thumb: rgba(255, 255, 255, 0.22); --sb-thumb-hover: rgba(255, 255, 255, 0.42); }
* { scrollbar-width: thin; scrollbar-color: var(--sb-thumb) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--sb-thumb); border-radius: 999px; border: 3px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background: var(--sb-thumb-hover); background-clip: padding-box; }
::-webkit-scrollbar-corner { background: transparent; }
body.glass ::-webkit-scrollbar-thumb { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35); }

/* Settings → Notifications */
.notif-device { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.notif-steps ol { margin: var(--s2) 0 0; padding-left: 1.2rem; display: grid; gap: var(--s1); }
.notif-prefs { display: grid; gap: var(--s2); }
.notif-row { align-items: flex-start; }
.notif-row .cell-sub { display: block; }
.notif-sub { margin-left: 1.6rem; }

/* Swipe left for timestamps (app/index.html, stampOf). The times live just
   past the right edge; --reveal (0–72px) is how far the finger has pulled. */
.msgs { --reveal: 0px; --reveal-o: 0; }
.msg { position: relative; transition: transform .25s ease; }
.msg.out { transform: translateX(calc(-1 * var(--reveal))); }
.msg-time {
  position: absolute; top: 50%; left: 100%; width: 64px; margin-left: 8px;
  transform: translate(calc(-1 * var(--reveal)), -50%);
  font-size: 11px; line-height: 1.25; color: var(--text-3); text-align: left; white-space: nowrap;
  pointer-events: none; opacity: var(--reveal-o); transition: transform .25s ease, opacity .25s ease;
}
.msg.out .msg-time { transform: translateY(-50%); }
.msgs.revealing .msg, .msgs.revealing .msg-time { transition: none; }

/* Account slots on Social → Accounts. */
.seat-card { margin-bottom: var(--s4); }
.seat-row { display: flex; align-items: center; justify-content: space-between; gap: var(--s4); flex-wrap: wrap; }
.seat-big { font-size: 26px; font-weight: 750; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.seat-dots { display: flex; gap: 6px; flex-wrap: wrap; }
.seat-dot { width: 22px; height: 22px; border-radius: 6px; border: 2px dashed var(--border-strong); }
.seat-dot.on { border-style: solid; border-color: var(--ok); background: var(--ok-soft); }
.seat-dot.extra { border-color: var(--accent-line); }
.seat-dot.extra.on { border-color: var(--accent); background: var(--accent-soft); }
.seat-confirm { margin-top: var(--s3); padding: var(--s3); border: 1px solid var(--accent-line); border-radius: var(--r); background: var(--accent-soft); display: grid; gap: var(--s2); }

/* Library storage meter. */
.usage-row { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s3); }
.usage-bar { height: 8px; border-radius: 999px; background: var(--surface-3); overflow: hidden; margin-top: var(--s2); }
.usage-bar span { display: block; height: 100%; background: var(--ok); border-radius: inherit; }
/* Usage costs → Profit: a business's per-kind lines, opened under its row. */
.tbl tr.usage-detail > td { background: var(--surface-2); padding-top: 0; }
.usage-bar.warn span { background: var(--warn); }
.usage-bar.full span { background: var(--danger); }

/* ------------------------------------------------------- the assistant
   css/assistant.css styles the chat widget; the CRM only tells it where it
   sits. Above the page, the bottom tab bar (50) and the dialer (60); below the
   drawers (70/75), sheets (80/90) and toasts (200), so the app's own dialogs
   always cover it. Its outer lift is the CRM's own shadow token, which the
   dark and glass themes already redefine. */
:root {
  --sa-z: 65;
  --sa-lift: var(--shadow-3);
  --sa-bottom-clear: var(--tabbar-h, 0px);
}

/* ------------------------------------------------------- in-page dialogs
   formDialog() in app/index.html: the CRM's own box for anything it asks,
   in place of the browser's prompt() and confirm() (15 Oct 2026). A <dialog>
   opened with showModal() sits in the top layer, above drawers and sheets,
   and traps focus until it closes. The footer stays put while a long form
   scrolls, so Done is always in reach. */
.app-dialog {
  width: min(560px, calc(100vw - 32px)); max-height: min(88vh, 900px);
  padding: 0; border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); color: var(--text); box-shadow: var(--shadow-3);
}
.app-dialog::backdrop { background: rgba(12, 8, 24, .45); }
.app-dialog-form { display: flex; flex-direction: column; max-height: inherit; }
.app-dialog-title { margin: 0; padding: var(--s4) var(--s5); font-size: 16px; border-bottom: 1px solid var(--border); }
.app-dialog-body { flex: 1; overflow-y: auto; padding: var(--s4) var(--s5); }
.app-dialog-intro { margin: 0 0 var(--s4); font-size: 14px; line-height: 1.5; white-space: pre-line; }
.app-dialog-hint { font-size: 12px; font-weight: 400; color: var(--text-2); }
.app-dialog .req { color: var(--danger-text); }
.app-dialog-error {
  margin: var(--s4) 0 0; padding: var(--s3) var(--s4); border-radius: var(--r);
  background: var(--danger-soft); color: var(--danger-text); font-size: 13px; font-weight: 550;
}
.app-dialog-foot {
  display: flex; justify-content: flex-end; gap: var(--s2);
  padding: var(--s3) var(--s5); border-top: 1px solid var(--border);
}
.app-dialog-foot .btn.primary { min-width: 96px; }
.app-dialog-foot .btn.primary.danger { background: var(--danger); border-color: transparent; color: #fff; }
.app-dialog select:disabled { opacity: .55; }
@media (max-width: 40rem) {
  .app-dialog { width: calc(100vw - 16px); max-height: 92vh; }
  .app-dialog-foot .btn { flex: 1; }
}

/* The contact drawer's Pipeline panel: one block per deal, its pipeline and
   stage side by side (a column each on a phone, from .field-grid). */
.deal-pick { padding: var(--s2) 0 var(--s3); border-bottom: 1px solid var(--border); }
.deal-pick:last-child { border-bottom: 0; }
.deal-pick-head { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s2); font-size: 13.5px; }
.deal-pick-head .deal-name { min-width: 0; overflow-wrap: anywhere; }
.deal-pick-head b { margin-left: auto; font-variant-numeric: tabular-nums; }
.drawer .deal-pick .field-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.app-dialog .app-dialog-check { flex-direction: row; align-items: center; gap: var(--s2); font-size: 14px; font-weight: 500; color: var(--text); }
.app-dialog .app-dialog-check input { width: 18px; height: 18px; flex: none; padding: 0; margin: 0; accent-color: var(--accent); }

/* Tasks (15 Oct 2026): the contact on a task row opens them; the bell says an
   alert is set. */
.link-btn { padding: 0; border: 0; background: none; color: var(--accent); font: inherit; cursor: pointer; text-align: left; }
.link-btn:hover { text-decoration: underline; }
.task-when { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.task-when svg, td[data-label="Alert"] svg { width: 13px; height: 13px; vertical-align: -2px; }

/* Photos and files on a contact (15 Oct 2026). Square tiles, three or four
   to a row in the drawer; a PDF shows its name. The remove × sits on the
   corner and is always visible — a phone has no hover to reveal it. */
.cf-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: var(--s2); margin-top: var(--s3); }
.cf-tile { position: relative; aspect-ratio: 1; border-radius: var(--r); overflow: hidden; border: 1px solid var(--border); background: var(--surface-2); }
.cf-open { display: block; width: 100%; height: 100%; padding: 0; border: 0; background: none; cursor: zoom-in; color: inherit; text-decoration: none; }
.cf-open img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cf-doc .cf-open { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--s1); padding: var(--s2); cursor: pointer; text-align: center; font-size: 11.5px; line-height: 1.3; }
.cf-doc .cf-open svg { width: 26px; height: 26px; color: var(--text-2); }
.cf-doc .cf-open span { overflow: hidden; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow-wrap: anywhere; }
.cf-x {
  position: absolute; top: 4px; right: 4px; width: 26px; height: 26px; border-radius: 50%;
  border: 0; background: rgba(12, 8, 24, .62); color: #fff; font-size: 16px; line-height: 1; cursor: pointer;
}
.cf-x:hover { background: var(--danger); }
#files-panel .btn svg { width: 15px; height: 15px; }

/* The webcam box and the photo viewer use the in-page dialog. */
.cam-dialog { width: min(720px, calc(100vw - 16px)); }
.cam-dialog video, .cam-dialog img { display: block; width: 100%; max-height: 60vh; object-fit: contain; border-radius: var(--r); background: #000; }
.cam-dialog .btn svg { width: 16px; height: 16px; }
.photo-view { width: min(1100px, calc(100vw - 16px)); }
.photo-view .app-dialog-body { padding: var(--s2); display: grid; place-items: center; background: #000; }
.photo-view img { max-width: 100%; max-height: 74vh; object-fit: contain; display: block; }

/* Deal cards show what each pipeline is set to (15 Oct 2026): a muted label
   before a value, long text cut to one line, tags as small pills. */
.deal .m .k { color: var(--text-3); font-weight: 600; }
.deal .m .ell { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.deal .m svg { width: 13px; height: 13px; flex: none; }
.deal .m.tags { flex-wrap: wrap; gap: 4px; }
.deal .m.tags .pill { font-size: 11px; padding: 1px 7px; }

/* Edit contact data: the example card in a stage column, then the boxes. */
.app-dialog-sub { margin: var(--s2) 0 0; font-size: 13px; font-weight: 650; color: var(--text); }
.card-preview { display: grid; place-items: center; padding: var(--s3); border-radius: var(--r-lg); background: var(--surface-3); }
.card-preview-col { width: min(280px, 100%); }
.card-preview-head { font-size: 12px; font-weight: 650; color: var(--text-2); margin-bottom: var(--s2); }
.card-preview .deal { cursor: default; }
.card-preview .deal:hover { transform: none; }
.card-preview-note { margin: var(--s2) 0 0; font-size: 11.5px; color: var(--text-3); text-align: center; }

/* Fields & values tags (15 Oct 2026): Default stands out and carries a shield
   (it can't be deleted); a website tag is a quiet pill; no tag is dashed, so
   what still needs one is easy to spot. */
.pill.tag-default { background: var(--accent-soft); color: var(--accent); font-weight: 650; display: inline-flex; align-items: center; gap: 4px; }
.pill.tag-default svg { width: 12px; height: 12px; }
.pill.tag-site { background: var(--info-soft); color: var(--info-text); }
.pill.tag-none { background: none; border: 1px dashed var(--border-strong); color: var(--text-3); }
.tag-filter .chip { cursor: pointer; font: inherit; }
.tag-filter .chip .cell-sub { margin-left: 2px; }
.inline-form input[name="tag"] { min-width: 170px; }

/* "New leads go to …" under the board's header (15 Oct 2026). */
.lead-route { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin: calc(-1 * var(--s2)) 0 var(--s3); font-size: 13px; color: var(--text-2); }
.lead-route svg { width: 14px; height: 14px; color: var(--accent); }
.lead-route b { color: var(--text); font-weight: 600; }
/* A star in a menu item, sized and placed like the icons beside it. */
.menu-item .menu-glyph { width: 18px; flex: none; text-align: center; font-size: 15px; line-height: 1; color: var(--accent); }
#pipeline-actions svg { width: 14px; height: 14px; }

/* "This contact is already in the CRM" (15 Oct 2026): saved vs just entered,
   the lines that differ marked, and a pick on each one when merging. */
.dup-table th:first-child { width: 28%; color: var(--text-3); font-weight: 600; }
.dup-table thead th { font-size: 12px; color: var(--text-2); }
.dup-table td { overflow-wrap: anywhere; }
.dup-table tr.dup-differs td, .dup-table tr.dup-differs th { background: var(--warn-soft); }
.dup-table i { color: var(--text-3); font-style: normal; }
.dup-pick { display: inline-flex; align-items: flex-start; gap: 6px; cursor: default; }
.dup-pick input { display: none; }
.dup-pick.dup-live { cursor: pointer; }
.dup-pick.dup-live input { display: inline-block; margin-top: 3px; accent-color: var(--accent); }
.dup-choice { margin: var(--s4) 0 0; padding: 0; border: 0; display: grid; gap: var(--s2); }
.dup-choice legend { font-size: 13px; font-weight: 650; margin-bottom: var(--s2); }
.dup-choice label { display: flex; align-items: flex-start; gap: var(--s2); font-size: 13.5px; line-height: 1.45; cursor: pointer; }
.dup-choice input { margin-top: 3px; accent-color: var(--accent); flex: none; }
.dup-both { margin-top: var(--s3); }

/* "Ring me in the CRM after hours" on Settings → Notifications (19 Oct 2026). */
.late-ring { margin-top: var(--s4); padding-top: var(--s4); border-top: 1px solid var(--border); display: grid; gap: var(--s3); }
.late-ring h4 { margin: 0; font-size: 14px; }
.late-ring > .cell-sub { margin: 0; }
.late-num { padding: var(--s3); border: 1px solid var(--border); border-radius: var(--r); display: grid; gap: var(--s3); }
.late-num .mono { color: var(--text-3); font-size: 12px; margin-left: 4px; }
.late-days { display: grid; gap: var(--s2); }
.late-days .cell-sub { margin: 0; }
.late-days .btn { justify-self: start; }
.late-cell { display: grid; gap: var(--s1); font-size: 13px; font-weight: 600; color: var(--text-2); }
.late-cell input { max-width: 240px; padding: 8px var(--s3); border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface); color: var(--text); font: inherit; font-weight: 400; }
.late-cell .cell-sub { font-weight: 400; }

/* Live email preview (Settings → Email, Reports → Bulk messages): the form on
   the left, the finished email on the right on a wide screen, stacked on a
   narrow one. The email is drawn at its real width and scaled to fit. */
.email-split { display: grid; gap: var(--s5); grid-template-columns: minmax(0, 1fr); align-items: start; }
@media (min-width: 1100px) {
  .email-split { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); }
  .email-split > .email-live { position: sticky; top: var(--s4); max-height: calc(100dvh - 120px); overflow-y: auto; }
}
.email-live { border: 1px solid var(--border); border-radius: var(--r-md, 12px); background: var(--surface); padding: var(--s3); }
.email-live-head { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s3); flex-wrap: wrap; }
.email-live-head b { font-size: 13px; }
.email-inbox { display: grid; grid-template-columns: 34px minmax(0, 1fr); gap: var(--s2); align-items: center; padding: var(--s2) var(--s3); margin-bottom: var(--s3); border-radius: 10px; background: #ffffff; color: #1f1b2e; }
.email-inbox .av { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; font-weight: 700; font-size: 14px; color: #fff; overflow: hidden; }
.email-inbox .av img { max-width: 26px; max-height: 26px; }
.email-inbox .from { font-size: 13px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.email-inbox .line { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.email-inbox .line .pre { color: #6b6680; font-weight: 400; }
.email-fit { position: relative; overflow: hidden; border-radius: 10px; background: #f2f0ea; }
.email-fit iframe { display: block; border: 0; background: #f2f0ea; transform-origin: top left; }
.email-fit.phone { background: #1d1a25; padding: 14px 0; }
.email-fit.phone iframe { border-radius: 18px; }
.email-live .note { font-size: 12px; color: var(--text-2); margin-top: var(--s2); }

/* The ↓ to the newest message (30 Oct 2026): over the messages, just above
   the reply box, while five or more messages are below the screen. Sticky in
   the wide layout, where the page scrolls rather than the messages. */
.msgs-down {
  position: sticky; bottom: var(--s3); align-self: flex-end; z-index: 3;
  margin: calc(-44px - var(--s3)) var(--s4) var(--s3) 0;
  width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--text);
  box-shadow: var(--shadow-2); cursor: pointer;
}
.msgs-down[hidden] { display: none; }
.msgs-down svg { width: 20px; height: 20px; }
body.glass .msgs-down { background: var(--glass-solid); -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); }

/* ------------------------------------------- inbox panes scroll on their own
 * Franklin, 3 Nov 2026: on a computer, scrolling the messages scrolled the
 * conversation list with them (and the reply box away). The inbox was a grid
 * whose one row grew to fit everything, so the page scrolled instead of
 * either pane. Wider than a phone, the inbox now fills the screen under its
 * tabs exactly: the list scrolls by itself, the messages scroll by
 * themselves, neither hands its scroll on to the page, and the contact's name
 * and the reply box stay put. (A phone already worked this way, below 760px.) */
@media (min-width: 761px) {
  .content.flush { overflow: hidden; }
  .inbox { grid-template-rows: minmax(0, 1fr); overflow: hidden; }
  .thread-list, .thread-view { min-height: 0; }
  .threads { min-height: 0; overscroll-behavior: contain; }
  .thread-view .thread-head, .thread-view .composer { flex: none; }
  .thread-view .msgs { min-height: 0; overscroll-behavior: contain; }
  /* The demo's banner sits above the header; leave room for it too. */
  body:has(#preview-bar:not([hidden])) .content.flush { height: calc(100dvh - var(--topbar-h) - 32px); }
}
