/* ============================================================================
   MESSAGES — the inbox, a thread, a group, Contacts and a person's record
   (public/manager-messages.js). ONE copy, read by BOTH apps.

   These rules lived in bento.css under `html[data-ui="bento"]`, which only the
   manager shell carries — so the day the crew app got Messages it had the markup
   and none of the styles. They were moved here rule for rule instead of being
   copied for the crew: a second copy is a second screen that drifts, and "todo lo
   que yo te pida tiene que ser en las dos versiones" is a promise about one screen.

   THE SCOPE IS `html[data-msg]`. manager-messages.js stamps that attribute on
   <html> in whichever shell loads it. It is one attribute on the root, exactly as
   `[data-ui="bento"]` was, so every selector here has the specificity it had in
   bento.css and wins and loses the same fights in the manager app as before. Do
   not shorten it to a bare `.msg-page`: that drops every rule one step and lets
   the shells' own `button` and `input` rules through.

   WHAT DID NOT MOVE, because it is about the manager SHELL and not this screen:
   hiding #cards on the Messages page, that page's body clearance and its desktop
   column width (bento.css), and the unread badge on the nav (system.css, THE NAV,
   drawn once for both apps).

   THE COLOURS are the app aliases bento.css defines for the manager (--ink,
   --panel, --accent…), each one a --sys-* token. The crew shell defines only some
   of those names, so they are declared on this screen's own two roots below, with
   the same values bento.css gives them. Nothing here types a hex.
   ========================================================================== */
html[data-msg] .msg-page,
html[data-msg] #msgLightbox {
  --ink: var(--sys-ink);
  --muted: var(--sys-muted);
  --line: var(--sys-line);
  --page: var(--sys-page);
  --panel: var(--sys-panel);
  --fill: var(--sys-fill);
  --fill-2: var(--sys-fill-2);
  --green: var(--sys-green);
  --green-soft: var(--sys-green-soft);
  --accent: var(--sys-accent);
  --accent-soft: var(--sys-accent-soft);
  --orange: var(--sys-orange);
  --orange-soft: var(--sys-orange-soft);
  --red: var(--sys-red-ink);
  --red-soft: var(--sys-red-soft);
}

/* Two panes from 900px up, one column below it (the crew is on phones). */
html[data-msg] .msg-page {
  font-family: var(--sys-font);
  color: var(--ink);
  /* This was a bare 96px with no env() — the one number in the file that opted out
     of the body rule and then forgot half of what the body rule was for. In a
     browser that is 28px of invisible slack. Inside the Capacitor shell, where the
     pill rises by the 34px home indicator and this padding does not, the last
     conversation in the list sits 6px UNDER the nav pill: check #4 of qa:safe-area
     happening again, in the one page that check does not look at, because the page
     re-declared the clearance instead of inheriting it.
     --sys-scroll-clear is the same 96px + inset the body carries, from the same
     place, so a browser sees no change at all and the app gets its last row back. */
  padding: 0 0 var(--sys-scroll-clear);
}
/* Mobile: the list scrolls with the page; opening a thread takes the whole
   screen (above the nav pill at z-index 40) so the composer can sit on the
   bottom edge with nothing floating over it. */
html[data-msg] .msg-page .msg-thread { display: none; }
html[data-msg] .msg-page.thread-open .msg-list { display: none; }
html[data-msg] .msg-page.thread-open .msg-thread {
  display: flex;
  flex-direction: column;
  position: fixed;
  inset: 0 var(--sys-safe-r) 0 var(--sys-safe-l);
  z-index: 45;
  background: var(--page);
}

html[data-msg] .msg-listhd { display: flex; gap: 8px; padding: 4px 0 10px; }
html[data-msg] .msg-search {
  /* 44, not the 42 it shipped with: it sits beside a 44px "New" button, so the
     two now line up as well as clear the floor. */
  flex: 1; min-width: 0; min-height: var(--sys-tap); box-sizing: border-box;
  border: 0; border-radius: var(--sys-r-pill); background: var(--panel); box-shadow: var(--sys-shadow-card);
  padding: 0 14px; font-weight: var(--sys-w-medium); font-size: var(--sys-t-field); font-family: var(--sys-font); color: var(--ink); outline: none;
  -webkit-appearance: none;
}
html[data-msg] .msg-search::placeholder { color: var(--sys-placeholder); }
html[data-msg] .msg-search:focus { box-shadow: 0 0 0 2px var(--accent); }
html[data-msg] .msg-new {
  flex: none; border: 0; border-radius: var(--sys-r-pill); background: var(--accent); color: #fff;
  font-weight: var(--sys-w-semibold); font-size: var(--sys-t-lead); font-family: var(--sys-font); padding: 0 16px; min-height: var(--sys-tap); cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* The inbox filter is a segmented control too. */
html[data-msg] .msg-tabs { display: flex; gap: 2px; padding: 2px; margin-bottom: 10px; background: var(--sys-seg-track); border-radius: var(--sys-r-segment-outer); }
html[data-msg] .msg-tab {
  flex: 1 1 0; border: 0; border-radius: var(--sys-r-segment); padding: 8px 0; min-height: var(--sys-tap); cursor: pointer;
  background: none; color: var(--ink); font-weight: var(--sys-w-medium); font-size: var(--sys-t-body); font-family: var(--sys-font);
}
html[data-msg] .msg-tab.on { background: var(--panel); color: var(--ink); font-weight: var(--sys-w-semibold); box-shadow: var(--sys-shadow-segment); }
html[data-msg] .msg-listbody { min-height: 0; }
html[data-msg] .msg-rows { display: grid; gap: 6px; }
/* Groups are one .msg-rows and the threads under them are another, so the gap that
   separates every other pair of rows was missing between the last group and the
   first thread: the two cards touched. Seen in both apps on 2026-09-30. */
html[data-msg] .msg-rows + .msg-rows { margin-top: 6px; }
html[data-msg] .msg-row {
  display: flex; align-items: center; gap: 11px; width: 100%; text-align: left; cursor: pointer;
  background: var(--panel); border: 0; box-shadow: var(--sys-shadow-card); border-radius: var(--sys-r-card); padding: 11px 14px; min-height: 52px;
  font: inherit; color: var(--ink); -webkit-tap-highlight-color: transparent;
}
html[data-msg] .msg-row.on { box-shadow: 0 0 0 1.5px var(--accent); }
/* SWIPE A ROW (2026-10-01). The actions sit behind the row, painted only while it
   is pulled aside (.reveal) so no red shows at its rounded corners at rest. The
   row is the one that moves; pan-y leaves up-and-down to the list's own scroll. */
html[data-msg] .msg-sw { position: relative; border-radius: var(--sys-r-card); }
html[data-msg] .msg-sw > .msg-row {
  position: relative; z-index: 1; touch-action: pan-y;
  transition: transform .26s cubic-bezier(.2, .8, .2, 1);
}
html[data-msg] .msg-sw.open > .msg-row { transform: translateX(calc(-1 * var(--msg-sw-w, 168px))); }
html[data-msg] .msg-sw.drag > .msg-row { transition: none; }
html[data-msg] .msg-sw-acts {
  position: absolute; inset: 0; display: flex; justify-content: flex-end;
  border-radius: var(--sys-r-card); overflow: hidden; visibility: hidden;
}
html[data-msg] .msg-sw.reveal .msg-sw-acts { visibility: visible; }
html[data-msg] .msg-sw-btn {
  flex: none; width: 84px; min-height: var(--sys-tap); border: 0; color: #fff; cursor: pointer;
  font-family: var(--sys-font); font-weight: var(--sys-w-semibold); font-size: var(--sys-t-body);
}
html[data-msg] .msg-sw-btn.arch { background: var(--accent); }
html[data-msg] .msg-sw-btn.del { background: var(--red); }
@media (prefers-reduced-motion: reduce) { html[data-msg] .msg-sw > .msg-row { transition: none; } }
html[data-msg] .msg-row.unread .msg-rowsub { color: var(--ink); font-weight: var(--sys-w-semibold); }
html[data-msg] .msg-av {
  flex: none; width: 40px; height: 40px; border-radius: var(--sys-r-pill); display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent); font-weight: var(--sys-w-semibold); font-size: 13.5px; font-family: var(--sys-font);
}
html[data-msg] .msg-av.sm { width: 34px; height: 34px; border-radius: 11px; font-size: 12px; }
html[data-msg] .msg-rowmain { flex: 1; min-width: 0; display: grid; gap: 2px; }
html[data-msg] .msg-rowtop { display: flex; align-items: baseline; gap: 8px; }
/* The one label a list of people earns: she is past her own usual gap between jobs.
   It sits where a conversation row puts its timestamp, so the two kinds of row have
   the same skeleton and the eye does not have to re-learn the list on every tab. */
html[data-msg] .msg-due {
  flex: none; margin-left: auto; border-radius: var(--sys-r-pill); padding: 2px 8px;
  font-family: var(--sys-font); font-weight: var(--sys-w-semibold); font-size: var(--sys-t-caption);
  background: var(--sys-orange-soft); color: var(--sys-orange);
}
html[data-msg] .msg-rowtop b {
  flex: 1; min-width: 0; font-weight: var(--sys-w-semibold); font-size: var(--sys-t-lead); font-family: var(--sys-font);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
html[data-msg] .msg-rowtop i { flex: none; font-weight: var(--sys-w-medium); font-size: var(--sys-t-small); font-family: var(--sys-font); font-style: normal; color: var(--muted); }
html[data-msg] .msg-rowsub {
  font-weight: var(--sys-w-regular); font-size: var(--sys-t-body); font-family: var(--sys-font); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
html[data-msg] .msg-house {
  justify-self: start; max-width: 100%; border-radius: var(--sys-r-pill); background: var(--fill-2);
  color: var(--muted); font-weight: var(--sys-w-semibold); font-size: var(--sys-t-caption); font-family: var(--sys-font); padding: 3px 8px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
html[data-msg] .msg-badge {
  flex: none; min-width: 20px; height: 20px; border-radius: var(--sys-r-pill); background: var(--accent);
  color: #fff; font-weight: var(--sys-w-semibold); font-size: var(--sys-t-caption); font-family: var(--sys-font); display: grid; place-items: center; padding: 0 6px;
}
html[data-msg] .msg-empty {
  display: grid; gap: 6px; justify-items: center; text-align: center;
  border: 0; border-radius: var(--sys-r-card); background: var(--panel); box-shadow: var(--sys-shadow-card);
  padding: 30px 18px; color: var(--muted); font-weight: var(--sys-w-medium); font-size: var(--sys-t-lead); font-family: var(--sys-font);
}
html[data-msg] .msg-empty b { font-weight: var(--sys-w-bold); font-size: var(--sys-t-lead); font-family: var(--sys-font); color: var(--ink); }
html[data-msg] .msg-empty.sm { padding: 18px 14px; border: 0; }
/* the "pick a conversation" placeholder floats in the middle of the empty pane */
html[data-msg] .msg-empty.pick { margin: auto; border: 0; background: none; box-shadow: none; }
html[data-msg] .msg-skel { display: grid; gap: 6px; }
html[data-msg] .msg-skel i {
  display: block; height: 64px; border-radius: 16px;
  background: linear-gradient(90deg, var(--fill) 25%, var(--fill-2) 37%, var(--fill) 63%);
  background-size: 400% 100%; animation: msgShimmer 1.3s ease infinite;
}
html[data-msg] .msg-skel.thread i { height: 44px; border-radius: 18px; }
@keyframes msgShimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
/* Reduce Motion: a still skeleton (2026-09-17). Here, not in section 14, because
   that block is earlier in the file than the rule it has to beat. */
@media (prefers-reduced-motion: reduce) {
  html[data-msg] .msg-skel i { animation: none; }
}

/* ---- thread ---- */
html[data-msg] .msg-thead {
  flex: none; display: flex; align-items: center; gap: 10px;
  padding: calc(10px + var(--sys-safe-t)) 14px 10px;
  border-bottom: 1px solid var(--line); background: var(--panel);
}
/* The other half of the pair (2026-09-19): this header carries Back and the contact's
   name inside the fixed .msg-thread layer, so it pays the status bar in a browser and
   gives it back once html.ni-top says the shell has paid it. */
html.ni-top[data-msg] .msg-thead { padding-top: 10px; }
/* It carries a word now ("\u2039 Contacts"), so it is sized by its text rather than by a
   fixed square \u2014 with a floor at the tap target, never under it. */
html[data-msg] .msg-back {
  flex: none; min-width: var(--sys-tap); height: var(--sys-tap); padding: 0 12px 0 8px;
  border-radius: var(--sys-r-pill); border: 0; display: inline-flex; align-items: center; gap: 2px;
  background: var(--panel); box-shadow: var(--sys-shadow-card); color: var(--accent); font-weight: var(--sys-w-medium); font-size: var(--sys-t-lead); line-height: 1; font-family: var(--sys-font); cursor: pointer; white-space: nowrap;
}
html[data-msg] .msg-who { flex: 1; min-width: 0; display: grid; }
html[data-msg] .msg-who b { font-weight: var(--sys-w-semibold); font-size: var(--sys-t-headline); font-family: var(--sys-font); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
html[data-msg] .msg-who span { font-weight: var(--sys-w-regular); font-size: var(--sys-t-body); font-family: var(--sys-font); color: var(--muted); white-space: nowrap; }
html[data-msg] .msg-tacts { flex: none; display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
html[data-msg] .msg-chip {
  border: 0; background: var(--panel); box-shadow: var(--sys-shadow-btn); color: var(--accent);
  /* 44, like the crew's .jz-chip ("44, not 34: these are the filters on the
     screen the crew lands on") — same component, same floor. An expander is not
     an option here: .msg-tacts wraps to a second row with a 8px row-gap, so any
     vertical growth would put one chip's hit box on top of the chip above it. */
  border-radius: var(--sys-r-pill); padding: 8px 12px; min-height: var(--sys-tap); font-weight: var(--sys-w-semibold); font-size: var(--sys-t-small); font-family: var(--sys-font); cursor: pointer;
  max-width: 42vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
html[data-msg] .msg-chip.on { background: var(--accent-soft); box-shadow: none; color: var(--accent); }
/* Phones (under the two-pane breakpoint, system.css): the name + number keep the
   first row to themselves; the job chip and Archive drop to a second row instead
   of squeezing the name to an ellipsis. */
@media (max-width: 599px) {
  html[data-msg] .msg-thead { flex-wrap: wrap; row-gap: 8px; }
  html[data-msg] .msg-thead .msg-tacts { flex: 1 0 100%; justify-content: flex-start; }
  html[data-msg] .msg-chip { max-width: 62vw; }
  /* …and that second row stays ONE row. A linked address (244px) beside Archive and
     Delete overran the 365px of a 393px phone, so Delete wrapped to a third line and
     the header took 167px from the messages. The address is the one that gives way —
     it already ellipsizes — and the verbs keep their words. */
  html[data-msg] .msg-thead .msg-tacts { flex-wrap: nowrap; align-items: center; min-width: 0; }
  html[data-msg] .msg-tacts > * { flex: none; }
  html[data-msg] .msg-tacts > .msg-chip.on,
  html[data-msg] .msg-tacts > .msg-house { flex: 0 1 auto; min-width: 0; }
}
html[data-msg] .msg-scroll {
  flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain; padding: 14px; display: flex; flex-direction: column; gap: 3px;
}
/* Chat convention: short threads sit on the composer, not under the header.
   margin-top:auto on the first child is the scroll-safe way to do it — a
   justify-content:flex-end scroller makes its own overflow unreachable. */
html[data-msg] .msg-scroll > *:first-child { margin-top: auto; }
/* \u2026but a RECORD is not a chat. The same convention that sits three messages on the
   composer pushed a contact's whole card to the bottom of an empty screen, with the
   gap above it. A record reads from the top, like any other page. */
html[data-msg] .msg-scroll.record { justify-content: flex-start; }
html[data-msg] .msg-scroll.record > *:first-child { margin-top: 0; }
html[data-msg] .msg-older {
  align-self: center; border: 0; background: var(--panel); box-shadow: var(--sys-shadow-btn); color: var(--accent);
  border-radius: var(--sys-r-pill); padding: 8px 14px; min-height: 36px; font-weight: var(--sys-w-semibold); font-size: var(--sys-t-body); font-family: var(--sys-font); cursor: pointer; margin-bottom: 8px;
}
/* The chat's day separator carries "Today" / "Yesterday" / "Monday" / a date —
   a date, not a section eyebrow, so it is sentence case. 2026-09-06 wave 2. */
html[data-msg] .msg-day {
  align-self: center; margin: 14px 0 8px; color: var(--muted);
  font-weight: var(--sys-w-semibold); font-size: var(--sys-t-caption); font-family: var(--sys-font); letter-spacing: 0; text-transform: none;
}
html[data-msg] .msg-b { display: flex; flex-direction: column; max-width: min(76%, 460px); margin-bottom: 5px; }
html[data-msg] .msg-b.in { align-self: flex-start; align-items: flex-start; }
html[data-msg] .msg-b.out { align-self: flex-end; align-items: flex-end; }
html[data-msg] .msg-bub {
  border-radius: var(--sys-r-card); padding: 9px 13px; font-weight: var(--sys-w-regular); font-size: var(--sys-t-lead); line-height: 1.38; font-family: var(--sys-font);
  overflow-wrap: anywhere; white-space: normal;
}
html[data-msg] .msg-b.in .msg-bub { background: var(--fill-2); color: var(--ink); border-bottom-left-radius: 6px; }
/* Outgoing is the accent — the bubble colour every phone already uses. */
html[data-msg] .msg-b.out .msg-bub { background: var(--accent); color: #fff; border-bottom-right-radius: 6px; }
html[data-msg] .msg-b.out.pending .msg-bub { background: var(--accent); opacity: .65; }
html[data-msg] .msg-b.out.bad .msg-bub { background: var(--red); }
html[data-msg] .msg-bub a { color: inherit; text-decoration: underline; }
html[data-msg] .msg-meta { margin-top: 3px; font-weight: var(--sys-w-medium); font-size: var(--sys-t-caption); font-family: var(--sys-font); color: var(--muted); }
html[data-msg] .msg-b.bad .msg-meta { color: var(--red); }
html[data-msg] .msg-err {
  margin-top: 3px; max-width: 100%; color: var(--red); background: var(--red-soft);
  border-radius: var(--sys-r-control); padding: 6px 9px; font-weight: var(--sys-w-semibold); font-size: var(--sys-t-small); line-height: 1.35; font-family: var(--sys-font); overflow-wrap: anywhere;
}
/* 44, not 36: the one control under a failed text \u2014 in a thread and, since a group
   send got its Retry back, under a group's bubble \u2014 is tapped by a thumb in a
   driveway like every other one here. */
html[data-msg] .msg-retry {
  margin-top: 5px; border: 0; background: var(--red-soft); color: var(--red);
  border-radius: var(--sys-r-pill); padding: 8px 13px; min-height: var(--sys-tap); font-weight: var(--sys-w-semibold); font-size: var(--sys-t-small); font-family: var(--sys-font); cursor: pointer;
}
html[data-msg] .msg-media { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 4px; }
html[data-msg] .msg-img { border: 0; padding: 0; background: none; cursor: pointer; line-height: 0; }
html[data-msg] .msg-img img {
  width: 148px; max-width: 100%; height: auto; border-radius: var(--sys-r-control);
  border: 0; box-shadow: var(--sys-shadow-card); display: block;
}

/* ---- composer ---- */
html[data-msg] .msg-composer {
  flex: none; border-top: 1px solid var(--line); background: var(--panel);
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
}
html[data-msg] .msg-input {
  width: 100%; box-sizing: border-box; min-height: 46px; max-height: 132px; resize: none;
  border: 0; border-radius: var(--sys-r-control); background: var(--fill);
  padding: 12px 14px; font-weight: var(--sys-w-regular); font-size: var(--sys-t-field); line-height: 1.35; font-family: var(--sys-font); color: var(--ink); outline: none;
}
html[data-msg] .msg-input::placeholder { color: var(--sys-placeholder); }
html[data-msg] .msg-input:focus { box-shadow: 0 0 0 2px var(--accent); }
html[data-msg] .msg-input:disabled { opacity: .6; }
html[data-msg] .msg-comrow { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
/* ---- photos waiting to be sent ----
   The tray sits ABOVE the text box, so a queued photo never pushes Send off the row
   and the thumbnail is the proof of what is about to go out. 44px on the remove
   button because it sits over a picture and a near-miss deletes the wrong thing. */
html[data-msg] .msg-tray { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 8px; }
html[data-msg] .msg-tray-i { position: relative; display: inline-block; line-height: 0; }
html[data-msg] .msg-tray-i img {
  width: 76px; height: 76px; object-fit: cover; border-radius: var(--sys-r-control);
  box-shadow: var(--sys-shadow-card); display: block;
}
html[data-msg] .msg-tray-x {
  position: absolute; top: -8px; right: -8px; width: 44px; height: 44px;
  display: grid; place-items: center; border: 0; background: none; cursor: pointer;
  color: var(--ink); font-size: 17px; line-height: 1;
}
html[data-msg] .msg-tray-x::before {
  content: ""; position: absolute; width: 24px; height: 24px; border-radius: 50%;
  background: var(--panel); box-shadow: var(--sys-shadow-card);
}
html[data-msg] .msg-tray-x > * { position: relative; }
html[data-msg] .msg-tray-load { font-size: var(--sys-t-caption); color: var(--muted); font-family: var(--sys-font); }
/* The attach control is a <label> wrapping a hidden file input: the whole pill is
   the tap target, which is why it carries the 44px floor rather than the input. */
html[data-msg] .msg-attach {
  display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 10px;
  border-radius: var(--sys-r-pill); cursor: pointer; color: var(--sys-accent);
  font-family: var(--sys-font); font-size: var(--sys-t-caption); font-weight: var(--sys-w-semibold);
}
html[data-msg] .msg-attach:has(input:disabled) { opacity: .45; cursor: default; }
/* Templates (Notification Center wave 3): the Photo button's look, as a button. */
html[data-msg] .msg-tplbtn { border: 0; background: none; }
html[data-msg] .msg-tplbtn[disabled] { opacity: .45; cursor: default; }
html[data-msg] .msg-count { flex: 1; min-width: 0; font-weight: var(--sys-w-medium); font-size: var(--sys-t-caption); font-family: var(--sys-font); color: var(--muted); }
html[data-msg] .msg-send {
  flex: none; border: 0; border-radius: var(--sys-r-pill); background: var(--accent); color: #fff;
  font-weight: var(--sys-w-semibold); font-size: var(--sys-t-lead); font-family: var(--sys-font); padding: 0 20px; min-height: var(--sys-tap); cursor: pointer;
}
html[data-msg] .msg-send[disabled] { opacity: .45; cursor: default; }
html[data-msg] .msg-send.wide { width: 100%; min-height: 48px; }
html[data-msg] .msg-optout {
  border-radius: var(--sys-r-control); background: var(--orange-soft); color: var(--orange);
  padding: 10px 12px; font-weight: var(--sys-w-semibold); font-size: var(--sys-t-body); line-height: 1.4; font-family: var(--sys-font); margin-bottom: 8px;
}

/* ---- group chats ----
   A group is a conversation, so it is drawn with the conversation's own parts: the
   same row, the same bubbles, the same composer. Only three things here are new —
   the mark that says a row is a group, the name over a bubble when the speaker
   changes, and the sheet that picks the people. */
html[data-msg] .msg-av.group { background: var(--sys-seg-track); color: var(--muted); font-size: 11px; letter-spacing: 1px; }
/* Who said it, over the first of that person's run of bubbles. Aligned to the
   bubble's own left edge, the width of the avatar column away from nothing. */
html[data-msg] .msg-said {
  align-self: flex-start; margin: 6px 0 3px 2px; color: var(--muted);
  font-weight: var(--sys-w-semibold); font-size: var(--sys-t-caption); font-family: var(--sys-font);
}
/* In a group the opt-out notice names people instead of speaking about "this
   contact", so it sits in the header where the names are, not over the composer.
   The header is a FLEX row, so the way to take a line of your own there is
   flex-basis 100%, not grid-column \u2014 and the row is told it may wrap at every
   width, not only under the narrow media query below, or the banner is squeezed in
   beside the Back button on a wide screen. Wrapping changes nothing when nothing
   needs to wrap. */
html[data-msg] .msg-thead { flex-wrap: wrap; row-gap: 8px; }
html[data-msg] .msg-optout.room { flex: 1 0 100%; margin: 8px 0 0; }

html[data-msg] .msg-pick-scrim { position: fixed; inset: 0; background: var(--sys-scrim); z-index: 41000; }
/* THE SHEET IS .msg-picker. `.msg-pick` is a house row in the Link-a-job sheet
   (further down), and for a while this rule carried that name too. Same selector,
   same weight, so the later one won wherever they overlapped and the earlier one
   leaked wherever they did not: this sheet was drawn as a flex row, and those rows
   were position:fixed on top of each other. One name, one thing. */
html[data-msg] .msg-picker {
  position: fixed; left: calc(10px + var(--sys-safe-l)); right: calc(10px + var(--sys-safe-r)); bottom: var(--sys-sheet-bottom);
  max-width: 520px; margin: 0 auto; z-index: 41001; box-sizing: border-box;
  background: var(--panel); border-radius: var(--sys-r-sheet); box-shadow: var(--sys-shadow-sheet);
  padding: 16px; display: flex; flex-direction: column; gap: 10px;
  max-height: min(82vh, calc(var(--sys-kb-view-h, 100dvh) - 8dvh - var(--sys-safe-t, 0px)));
}
/* A FLEX COLUMN, NOT A GRID (2026-10-01). Grid rows sized to their content never
   shrink under a max-height: with fourteen people the list ran out through the
   bottom of the sheet, would not scroll, and carried Start group off the screen —
   "no puedo bajar, no puedo crearlo". Now the header, the two fields and the foot
   keep their size and the list alone takes what is left, and scrolls. */
html[data-msg] .msg-picker > * { flex: none; }
html[data-msg] .msg-picker > .msg-pick-list { flex: 1 1 auto; overscroll-behavior: contain; }
html[data-msg] .msg-pick-hd { display: flex; align-items: center; justify-content: space-between; }
html[data-msg] .msg-pick-hd b { font-weight: var(--sys-w-bold); font-size: var(--sys-t-headline); font-family: var(--sys-font); }
html[data-msg] .msg-pick-x {
  border: 0; background: none; color: var(--muted); font-size: 22px; line-height: 1;
  min-height: var(--sys-tap); min-width: var(--sys-tap); cursor: pointer; font-family: var(--sys-font);
}
html[data-msg] .msg-pick-name,
html[data-msg] .msg-pick-search {
  width: 100%; box-sizing: border-box; min-height: var(--sys-tap); border: 0; border-radius: var(--sys-r-control);
  background: var(--sys-fill); padding: 0 14px; color: var(--ink); outline: none;
  font-family: var(--sys-font); font-size: var(--sys-t-field); font-weight: var(--sys-w-medium); -webkit-appearance: none;
}
html[data-msg] .msg-pick-name:focus,
html[data-msg] .msg-pick-search:focus { box-shadow: 0 0 0 2px var(--accent); }
html[data-msg] .msg-pick-list { min-height: 0; overflow-y: auto; display: grid; gap: 4px; -webkit-overflow-scrolling: touch; }
html[data-msg] .msg-pick-row {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; cursor: pointer;
  border: 0; background: var(--sys-fill); border-radius: var(--sys-r-card); padding: 9px 13px; min-height: var(--sys-tap);
  font: inherit; color: var(--ink); -webkit-tap-highlight-color: transparent;
}
html[data-msg] .msg-pick-row.on { box-shadow: 0 0 0 1.5px var(--accent); }
/* Somebody with no mobile number cannot be in a group that texts, so the row is
   there — she exists — and it does not pretend to be a choice. */
html[data-msg] .msg-pick-row[disabled] { opacity: .45; cursor: default; }
html[data-msg] .msg-pick-tick {
  flex: none; width: 22px; height: 22px; border-radius: var(--sys-r-pill); display: grid; place-items: center;
  background: var(--sys-seg-track); color: var(--accent); font-weight: var(--sys-w-bold); font-size: 13px; font-family: var(--sys-font);
}
html[data-msg] .msg-pick-row.on .msg-pick-tick { background: var(--accent); color: #fff; }
html[data-msg] .msg-pick-main { flex: 1; min-width: 0; display: grid; gap: 1px; }
html[data-msg] .msg-pick-main b { font-weight: var(--sys-w-semibold); font-size: var(--sys-t-lead); font-family: var(--sys-font); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
html[data-msg] .msg-pick-main i { font-style: normal; color: var(--muted); font-size: var(--sys-t-caption); font-family: var(--sys-font); }
html[data-msg] .msg-pick-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
html[data-msg] .msg-pick-foot span { color: var(--muted); font-size: var(--sys-t-body); font-family: var(--sys-font); font-weight: var(--sys-w-medium); }
html[data-msg] .msg-pick-go {
  flex: none; border: 0; border-radius: var(--sys-r-pill); background: var(--accent); color: #fff;
  padding: 0 18px; min-height: var(--sys-tap); cursor: pointer;
  font-family: var(--sys-font); font-weight: var(--sys-w-semibold); font-size: var(--sys-t-lead);
}
html[data-msg] .msg-pick-go[disabled] { opacity: .45; cursor: default; }

/* ---- sheets (new message / link a job) + photo lightbox ---- */
html[data-msg] .msg-sheetbk { position: fixed; inset: 0; background: var(--sys-scrim); z-index: 41000; }
/* Same story as #os2Edit: this sheet is where a new conversation's phone number and
   the search box are typed. --sys-sheet-bottom is max(10px, env(...)) at rest and
   rises with the keyboard, from the one redefinition in system.css. */
html[data-msg] .msg-sheet {
  position: fixed; left: calc(10px + var(--sys-safe-l)); right: calc(10px + var(--sys-safe-r)); bottom: var(--sys-sheet-bottom);
  max-width: 520px; margin: 0 auto; z-index: 41001; box-sizing: border-box;
  background: var(--panel); border-radius: var(--sys-r-sheet); box-shadow: var(--sys-shadow-sheet);
  padding: 16px; display: grid; gap: 10px; max-height: 82vh; overflow-y: auto;
  /* With the keyboard up the sheet rides above it, but 82vh is the whole screen's
     82%, so its head and ✕ went off the top of the glass while a number or a
     search was being typed. Capped by the room left above the keyboard, the same
     expression the Money and inventory sheets use. */
  max-height: min(82vh, calc(var(--sys-kb-view-h, 100dvh) - 8dvh - var(--sys-safe-t, 0px)));
}
html[data-msg] .msg-sheethd { display: flex; align-items: center; justify-content: space-between; }
html[data-msg] .msg-sheethd b { font-weight: var(--sys-w-bold); font-size: var(--sys-t-headline); font-family: var(--sys-font); }
/* The sheet's close X. 32px is what it looks like — a quiet circle opposite the
   sheet title — and the invisible expander makes it the 44 it has to be, the same
   trick .os2-mini-btn and the crew's .jz-scope use. -6px all round = 44x44. It
   may grow every way: its only neighbour in .msg-sheethd is the title text, and
   in #msgLightbox it is inset 14px from both edges. */
html[data-msg] .msg-x {
  border: 0; background: var(--fill-2); color: var(--ink); border-radius: var(--sys-r-pill);
  width: 32px; height: 32px; font-weight: var(--sys-w-semibold); font-size: 13px; font-family: var(--sys-font); cursor: pointer;
  position: relative;
}
html[data-msg] .msg-x::after { content: ""; position: absolute; inset: -6px; }
html[data-msg] .msg-picks { display: grid; gap: 5px; max-height: 40vh; overflow-y: auto; }
html[data-msg] .msg-pick {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 52px; text-align: left; cursor: pointer;
  border: 0; border-radius: var(--sys-r-control); background: var(--fill); padding: 8px 12px; font: inherit;
}
html[data-msg] .msg-pick.on { background: var(--accent-soft); box-shadow: 0 0 0 1.5px var(--accent); }
html[data-msg] .msg-pick span { min-width: 0; }
html[data-msg] .msg-pick b { display: block; font-weight: var(--sys-w-semibold); font-size: var(--sys-t-lead); font-family: var(--sys-font); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
html[data-msg] .msg-pick i { display: block; font-weight: var(--sys-w-regular); font-size: var(--sys-t-body); font-family: var(--sys-font); font-style: normal; color: var(--muted); }
/* ================= THE CUSTOMER RECORD, inside Messages =================
   Moved out of the old #directoryOverlay on 2026-09-29 when Contacts stopped being
   a second screen. The class prefix stayed `.dir-` so the record's own history \u2014
   and the pins written against it \u2014 survived the move; what did NOT come with it
   is everything that only made sense as an overlay: the fixed full-screen host, its
   own sticky header, its own list, chips and search. The record is drawn inside
   .msg-thread, so it inherits that pane's scrolling, its safe-area insets and its
   one back door. */
html[data-msg] .dir-card {
  background: var(--panel); border: 0; box-shadow: var(--sys-shadow-card); border-radius: var(--sys-r-card);
  padding: 16px; display: grid; gap: 12px; font-family: var(--sys-font);
}
html[data-msg] .dir-av {
  flex: none; width: 52px; height: 52px; border-radius: 15px; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent); font-weight: var(--sys-w-bold); font-size: 18px;
}
html[data-msg] .dir-name { display: flex; align-items: center; gap: 12px; }
html[data-msg] .dir-name .who { min-width: 0; }
html[data-msg] .dir-name p { margin: 0; color: var(--muted); font-size: var(--sys-t-body); font-weight: var(--sys-w-medium); }
html[data-msg] .dir-actions { display: grid; grid-template-columns: repeat(var(--dir-acts, 3), minmax(0, 1fr)); gap: 8px; }
html[data-msg] .dir-actions a {
  display: flex; align-items: center; justify-content: center; gap: 6px; min-height: 48px;
  border-radius: var(--sys-r-pill); font-weight: var(--sys-w-semibold); font-size: var(--sys-t-lead); text-decoration: none;
}
html[data-msg] .dir-actions a.email, html[data-msg] .dir-actions a.call { background: var(--accent); color: #fff; }
html[data-msg] .dir-actions a.text { background: var(--accent-soft); color: var(--accent); }
html[data-msg] .dir-kv { display: grid; gap: 6px; }
html[data-msg] .dir-kv .r { display: flex; justify-content: space-between; gap: 12px; font-size: 14px; }
html[data-msg] .dir-kv .r span { color: var(--muted); }
html[data-msg] .dir-kv .r b { font-weight: var(--sys-w-semibold); text-align: right; overflow-wrap: anywhere; }
/* Three figures, equal weight, because they answer one question between them: is
   this a good client. Tabular numerals so the columns line up. */
html[data-msg] .dir-nums { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
html[data-msg] .dir-num {
  background: var(--fill); border-radius: var(--sys-r-control); padding: 11px 10px;
  display: grid; gap: 2px; justify-items: center; text-align: center;
}
html[data-msg] .dir-num b { font-size: 19px; font-weight: var(--sys-w-bold); letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
html[data-msg] .dir-num span { font-size: var(--sys-t-caption); color: var(--muted); font-weight: var(--sys-w-semibold); letter-spacing: .03em; text-transform: uppercase; }
html[data-msg] .dir-num.owed b { color: var(--red); }
html[data-msg] .dir-cadence { font-size: var(--sys-t-lead); color: var(--muted); font-weight: var(--sys-w-medium); text-align: center; }
html[data-msg] .dir-standing { display: inline-block; border-radius: var(--sys-r-pill); padding: 3px 10px; font-size: var(--sys-t-caption); font-weight: var(--sys-w-semibold); }
html[data-msg] .dir-standing.active { background: var(--green-soft); color: var(--green); }
html[data-msg] .dir-standing.due { background: var(--sys-orange-soft); color: var(--sys-orange); }
html[data-msg] .dir-standing.dormant { background: var(--fill-2); color: var(--muted); }
html[data-msg] .dir-standing.new { background: var(--accent-soft); color: var(--accent); }
html[data-msg] .dir-sec { display: grid; gap: 7px; }
html[data-msg] .dir-sec > h3 { margin: 6px 0 0; font-size: var(--sys-t-caption); font-weight: var(--sys-w-semibold); letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
html[data-msg] .dir-job {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; background: var(--fill);
  border: 0; border-radius: var(--sys-r-control); padding: 10px 12px; min-height: var(--sys-tap);
  font: inherit; font-family: var(--sys-font); cursor: pointer; color: var(--ink);
}
/* A house that cannot be opened from this app is the same row and not a button
   (manager-messages.js houseRowHtml). A <div> does not get a button's border-box
   for free, and with width:100% and its padding it would run 24px off the card. */
html[data-msg] .dir-job.still { box-sizing: border-box; cursor: default; }
html[data-msg] .dir-job .j { flex: 1; min-width: 0; }
html[data-msg] .dir-job .j b { display: block; font-weight: var(--sys-w-semibold); font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
html[data-msg] .dir-job .j span { display: block; color: var(--muted); font-size: var(--sys-t-body); }
html[data-msg] .dir-job .m { flex: none; text-align: right; font-variant-numeric: tabular-nums; }
html[data-msg] .dir-job .m b { display: block; font-size: 14.5px; font-weight: var(--sys-w-semibold); }
html[data-msg] .dir-job .m span { display: block; font-size: var(--sys-t-caption); color: var(--muted); }
html[data-msg] .dir-job .m span.owed { color: var(--red); font-weight: var(--sys-w-semibold); }
html[data-msg] .dir-stale {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; background: var(--sys-orange-soft); color: var(--sys-orange);
  border-radius: var(--sys-r-control); padding: 9px 12px; font-size: var(--sys-t-lead); font-weight: var(--sys-w-semibold);
}
html[data-msg] .dir-link {
  border: 0; background: transparent; color: var(--accent); font: inherit; font-family: var(--sys-font);
  font-weight: var(--sys-w-medium); font-size: var(--sys-t-lead); min-height: var(--sys-tap); padding: 0 4px; cursor: pointer; text-decoration: underline;
}
html[data-msg] .dir-loading { color: var(--muted); font-size: var(--sys-t-lead); text-align: center; padding: 14px 0; }
/* the form */
html[data-msg] .dir-field { display: grid; gap: 5px; }
html[data-msg] .dir-field label { font-size: var(--sys-t-caption); font-weight: var(--sys-w-semibold); letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
html[data-msg] .dir-field input, html[data-msg] .dir-field select, html[data-msg] .dir-field textarea {
  min-height: var(--sys-tap); border-radius: var(--sys-r-control); border: 0; background: var(--fill); color: var(--ink);
  padding: 9px 12px; font-family: var(--sys-font); font-size: var(--sys-t-field); box-sizing: border-box; width: 100%;
}
html[data-msg] .dir-field textarea { min-height: 76px; resize: vertical; }
html[data-msg] .dir-formrow { display: flex; gap: 8px; }
html[data-msg] .dir-note { color: var(--muted); font-size: 11.5px; }
html[data-msg] .dir-btn {
  border: 0; border-radius: var(--sys-r-pill); min-height: var(--sys-tap); padding: 0 16px;
  font-family: var(--sys-font); font-weight: var(--sys-w-semibold); font-size: var(--sys-t-lead); cursor: pointer;
}
html[data-msg] .dir-btn.green { background: var(--accent); color: #fff; }
html[data-msg] .dir-btn.ghost { background: var(--panel); color: var(--accent); box-shadow: var(--sys-shadow-btn); }
html[data-msg] .dir-btn.danger { background: var(--panel); color: var(--red); box-shadow: var(--sys-shadow-btn); }

/* DELETE, and the way back from it. */
html[data-msg] .msg-chip.danger { background: var(--red-soft); color: var(--red); }
/* The undo bar sits above the nav pill and clears the home indicator. It is a bar
   and not a toast because a toast says what happened and this offers to take it
   back \u2014 a different job needs a different shape and a real tap target. */
html[data-msg] .msg-undo {
  position: fixed; left: var(--sys-center-x); transform: translateX(-50%); z-index: 60;
  /* --sys-nav-top is nav-h + nav-gap + the home indicator, composed once in
     system.css so nothing out here types env() or guesses the pill's height. A
     hand-typed "86px + env(...)" was both a second copy of that arithmetic and a
     double payment of the inset. 10px of air on top of it. */
  bottom: calc(var(--sys-nav-top) + 10px);
  display: flex; align-items: center; gap: 14px; max-width: min(520px, calc(100vw - 28px));
  background: var(--sys-ink); color: var(--sys-page); border-radius: var(--sys-r-pill);
  padding: 0 6px 0 16px; min-height: 48px; box-shadow: var(--sys-shadow-card);
  font-family: var(--sys-font); font-size: var(--sys-t-lead); font-weight: var(--sys-w-medium);
}
html[data-msg] .msg-undo span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
html[data-msg] .msg-undo button {
  flex: none; border: 0; background: transparent; color: var(--sys-blue-on-dark, #6cb2ff);
  font-family: var(--sys-font); font-weight: var(--sys-w-bold); font-size: var(--sys-t-lead);
  min-height: var(--sys-tap); padding: 0 12px; cursor: pointer;
}

html[data-msg] .msg-unlink {
  border: 0; background: var(--red-soft); color: var(--red); border-radius: var(--sys-r-pill);
  min-height: var(--sys-tap); font-weight: var(--sys-w-semibold); font-size: var(--sys-t-lead); font-family: var(--sys-font); cursor: pointer;
}
#msgLightbox { position: fixed; inset: 0; background: rgba(0, 0, 0, .92); z-index: 42000; display: none; place-items: center; }
#msgLightbox.open { display: grid; }
/* dvh, not vh: inside the shell the large viewport over-reports by up to 96px, so
   a photo sized against it overflows the glass it is being viewed on. */
#msgLightbox img { max-width: 94vw; max-height: 88dvh; object-fit: contain; }
#msgLightbox .msg-x { position: absolute; top: max(14px, var(--sys-safe-t)); right: calc(14px + var(--sys-safe-r)); background: rgba(255, 255, 255, .18); color: #fff; }
/* The other half of the pair (2026-09-19): the ✕ is the only way out of the photo
   viewer, so it clears the status bar — and stops paying for it under html.ni-top. */
html.ni-top #msgLightbox .msg-x { top: 14px; }
/* ---- desktop, an iPad, the open iPhone Duo: real two-pane inbox (system.css says why 600px) ---- */
@media (min-width: 600px) {
  html[data-msg] .msg-page {
    /* Conversations are short rows, so this list is narrower than the house list
       (220, growing to 30% on a desktop); the thread takes the rest — 341 of the open
       Duo's 585, so its header verbs and its composer row keep to one line. */
    display: grid; grid-template-columns: minmax(220px, 30%) minmax(0, 1fr);
    gap: 8px; align-items: stretch; height: calc(100dvh - 172px); min-height: 440px; padding-bottom: 0;
  }
  html[data-msg] .msg-page .msg-list {
    display: flex !important; flex-direction: column; min-height: 0; min-width: 0;
  }
  html[data-msg] .msg-page .msg-listbody { flex: 1; min-height: 0; overflow-y: auto; padding-right: 2px; }
  html[data-msg] .msg-page .msg-thread,
  html[data-msg] .msg-page.thread-open .msg-thread {
    display: flex !important; position: static; inset: auto; z-index: auto;
    flex-direction: column; min-height: 0; min-width: 0;
    border: 0; border-radius: var(--sys-r-card); background: var(--panel); box-shadow: var(--sys-shadow-card); overflow: hidden;
  }
  html[data-msg] .msg-page.thread-open .msg-list { display: flex !important; }
  html[data-msg] .msg-back { display: none; }
  /* In the open Duo's 341px thread the verbs drop under the name instead of over it;
     on a desktop the row is wide enough and never wraps. */
  html[data-msg] .msg-thead { padding: 12px 16px; flex-wrap: wrap; row-gap: 8px; }
  /* The composer's counter is one line; the row's air gives before it does. */
  html[data-msg] .msg-comrow { gap: 6px; }
  html[data-msg] .msg-count { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* Restated inside this branch so the companion above cannot drag the wide layout
     down to the phone's 10px. 2026-09-19. */
  html.ni-top[data-msg] .msg-thead { padding-top: 12px; }
  html[data-msg] .msg-chip { max-width: 220px; }
}

/* ============================================================================
   WHERE THE SCREEN SITS IN THE CREW APP, and what a thread needs when nobody can
   answer it. Nothing here changes what the screen looks like.
   ========================================================================== */
/* The crew's .app-shell already clears its nav for every screen it draws, so the
   list must not pay that clearance a second time. */
html[data-msg] .field-app .msg-page { padding-bottom: 0; }
/* Two panes on a wide screen (an iPad on its side, a desktop). The panes' height
   is "the screen minus what is above and below them", and 172px is the MANAGER's
   above-and-below. The crew's header is taller and its shell clears the nav itself,
   so with that number the thread ran 21px under the nav pill and the page scrolled
   60px further than the shell's own frame already makes it. 205 = 115 above (the
   framed header) + 78 for the nav pill and its gap + 12 of air, measured at 1200,
   1024 and 900 wide. */
@media (min-width: 600px) {
  html[data-msg] .field-app .msg-page { height: calc(100dvh - 205px); }
}
/* A THREAD NOBODY CAN ANSWER HAS NO COMPOSER UNDER IT. The composer is what paid
   the home indicator at the foot of the pane; without it the last bubble would sit
   under the bar. --sys-safe-b is the inset itself, last in the calc(), and the
   shell that pays it for the page never pays it for a fixed layer. */
html[data-msg] .msg-thread.reading .msg-scroll { padding-bottom: calc(14px + var(--sys-safe-b)); }
html.ni-bottom[data-msg] .msg-thread.reading .msg-scroll { padding-bottom: 14px; }
