/* FreshMesh Inbox — accent #0f8a6a taken from freshmesh.sg so the two read as one product. */
:root{
  --brand:#0f8a6a; --brand-dk:#0b6b52; --brand-lt:#e6f4ef;
  --ink:#12211d; --muted:#6b7d78; --line:#e2e8e6; --bg:#f6f8f7; --card:#fff;
  --out:#d8f2e6; --in:#fff; --warn:#b4451f; --radius:12px;
  --font:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
*{box-sizing:border-box}
html,body{height:100%}
body{margin:0;font-family:var(--font);color:var(--ink);background:var(--bg);font-size:15px;line-height:1.45}
a{color:inherit;text-decoration:none}
.muted{color:var(--muted)}

/* ── top bar ── */
.topbar{display:flex;align-items:center;gap:14px;height:56px;padding:0 16px;
  background:var(--brand);color:#fff;position:sticky;top:0;z-index:20}
.brand{font-weight:700;letter-spacing:-.2px}
.brand .muted{color:#bfe3d6;font-weight:500}
.topbar .spacer{flex:1}
.who{font-size:13px;color:#dff0e9}
/* white-space:nowrap (2026-09-17): without it, a header too narrow to fit "Sign out" on one line
   wrapped it mid-word - "out" dropped to a second line and was clipped at the right edge on a
   390px phone. See the max-width:480px block below for the rest of the mobile header fix. */
.logout{margin-left:12px;border:1px solid rgba(255,255,255,.45);padding:4px 10px;border-radius:999px;
  font-size:12px;white-space:nowrap}
.logout:hover{background:rgba(255,255,255,.15)}
.wa-status{font-size:12px;padding:3px 10px;border-radius:999px;background:rgba(255,255,255,.18)}
.wa-status.ok{background:rgba(255,255,255,.18)}
.wa-status.bad{background:#8c2f14;font-weight:600}

/* ── layout ── */
.layout{display:grid;grid-template-columns:340px 1fr;height:calc(100% - 56px)}
.list{border-right:1px solid var(--line);background:var(--card);display:flex;flex-direction:column;min-height:0}
/* position:relative anchors .newpill (the live-inbox "new messages" button) to the thread pane.
   Added 2026-09-17 with the live refresh — without it the pill positions against the viewport. */
.thread{display:flex;flex-direction:column;min-height:0;background:var(--bg);position:relative}
/* Shown only when a message arrives while the coordinator has scrolled up to read something
   older. Auto-scrolling them instead would yank the text out from under them mid-sentence. */
.newpill{position:absolute;left:50%;transform:translateX(-50%);bottom:86px;z-index:5;
  padding:7px 15px;font:inherit;font-size:13px;font-weight:600;border:0;border-radius:999px;
  background:var(--brand);color:#fff;cursor:pointer;box-shadow:0 2px 10px rgba(0,0,0,.22)}
.newpill:hover{background:var(--brand-dk)}

/* ── filters ── */
.filters{padding:12px;border-bottom:1px solid var(--line)}
/* Search input + go button, INLINE on one row (2026-09-17) - it used to be full-width with the
   button on its own row below, one of seven rows of chrome sitting on top of the conversation
   list. See index.php: the submit button itself is load-bearing (do not remove), this is only
   about where it sits. */
.searchrow{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.filters input[type=search]{flex:1;min-width:0;padding:9px 12px;border:1px solid var(--line);
  border-radius:999px;font:inherit;background:var(--bg)}
.filters input[type=search]:focus{outline:2px solid var(--brand);outline-offset:1px}
.chips{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}
.chip{font-size:12px;padding:4px 11px;border-radius:999px;border:1px solid var(--line);color:var(--muted)}
.chip.on{background:var(--brand-lt);border-color:var(--brand);color:var(--brand-dk);font-weight:600}
/* Tag filter chips get their own accent (matches .tagchip) so they read as a different KIND of
   filter from the role chips (Prospects/Customers/Blocked) directly above them. */
.chip.tag.on{background:#eef0fb;border-color:#3b3f8a;color:#3b3f8a}
.chip .chipcount{opacity:.6;font-weight:500;margin-left:3px}
.chip.modechip{background:var(--bg)}

/* ── stage filter chips (2026-09-17) ────────────────────────────────────────────────────────
   Charles: "at which stage" a conversation is - filterable the same way roles/tags are. Each
   stage gets its OWN colour (not one shared "on" accent like the role/tag chips above) even when
   NOT selected, because scanning the row for "which stages exist at all" is itself useful, and
   because Lost must never read anything like Won at a glance - see .crmpill below, same colours,
   same reasoning. */
.chip.stagechip{border-color:transparent;font-weight:600}
.chip.stagechip.on{box-shadow:inset 0 0 0 1.5px currentColor}
.stage-not-contacted{background:#eef1f0;color:#5b6b66}
.stage-contacted{background:#fff4de;color:#8a6a1a}
.stage-needs-measurements{background:#e7f0fb;color:#1f5c96}
.stage-quoted{background:#e3f2fd;color:#0b5f8a}
.stage-followed-up{background:#e3f7f7;color:#0e6b6b}
.stage-won{background:var(--brand-lt);color:var(--brand-dk)}
.stage-lost{background:#fdeceb;color:#8c2f14}
.stage-no-record{background:#f1f3f2;color:var(--muted);border:1px dashed var(--line)}
.stage-other{background:#f1f3f2;color:var(--muted);border:1px dashed var(--line)}

/* ── conversation list ── */
.convos{list-style:none;margin:0;padding:0;overflow-y:auto;flex:1}
.convos li{border-bottom:1px solid var(--line)}
.convos li a{display:block;padding:11px 14px}
.convos li:hover{background:var(--bg)}
.convos li.sel{background:var(--brand-lt)}
.convos li.sel a{box-shadow:inset 3px 0 0 var(--brand)}
.row1{display:flex;justify-content:space-between;align-items:baseline;gap:8px}
.name{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.time{font-size:11px;color:var(--muted);white-space:nowrap}
.row2{display:flex;align-items:center;gap:7px;margin-top:3px}
.prev{font-size:13px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.prev .you{color:var(--brand-dk);font-weight:600}

.badge{font-size:10px;font-weight:700;letter-spacing:.4px;text-transform:uppercase;
  padding:2px 7px;border-radius:4px;white-space:nowrap}
.badge-staff{background:#e4ecfb;color:#274b8f}
.badge-prospect{background:#fdf0dc;color:#96601a}
.badge-customer{background:var(--brand-lt);color:var(--brand-dk)}
.badge-blocked{background:#f1e3e3;color:#8c2f14}

/* ── tags (2026-09-17) ──────────────────────────────────────────────────────────────────────
   Charles: "Allow me to tag each person ... and to be able to see the tags to filter them."
   A human tag and an AI tag must never read as the same kind of decision at a glance - the AI
   variant carries a distinct colour AND a text badge, not just a hover title, because a title
   only shows up on hover and this must not depend on anyone thinking to hover. Text, not an
   emoji, for the same reason the attach icon above is an inline SVG: an emoji can render as an
   empty box with no emoji font, and this is the one distinction that must never silently vanish. */
.tagchips{display:inline-flex;flex-wrap:wrap;gap:4px;vertical-align:middle}
.tagchip{display:inline-flex;align-items:center;gap:3px;font-size:10px;font-weight:700;
  letter-spacing:.2px;padding:2px 8px;border-radius:999px;white-space:nowrap;
  background:#eef0fb;color:#3b3f8a;border:1px solid #d7dbf5}
.tagchip-ai{background:#fff8e6;color:#8a6d1a;border:1px dashed #d8b74a}
.tagchip-ai-mark{font-size:8px;font-weight:800;letter-spacing:.3px;background:#d8b74a;
  color:#4a3a08;border-radius:4px;padding:0 4px;line-height:1.5}
.row3{margin-top:3px}
.row3 .tagchip{font-size:9px;padding:1px 7px}

/* ── thread ── */
.placeholder{margin:auto;text-align:center;max-width:420px;padding:24px}
.placeholder h2{font-weight:600;margin:0 0 8px}
.thread-head{display:flex;flex-wrap:wrap;align-items:center;gap:12px;padding:10px 16px;
  background:var(--card);border-bottom:1px solid var(--line)}
.t-name{font-weight:650}
.t-sub{font-size:12px}
.back{display:none;font-size:26px;line-height:1;color:var(--muted);padding-right:4px}
.roleform{margin-left:auto;display:flex;gap:6px;align-items:center}
.roleform input,.roleform select{font:inherit;font-size:13px;padding:5px 8px;
  border:1px solid var(--line);border-radius:8px;background:var(--bg)}
.roleform input{width:150px}
.roleform button{font:inherit;font-size:13px;padding:5px 12px;border:0;border-radius:8px;
  background:var(--brand);color:#fff;cursor:pointer}
.roleform button:hover{background:var(--brand-dk)}

/* Tags on the open contact. flex-basis:100% forces this onto its own row below the name/role
   line regardless of viewport - .thread-head > div (mobile media query below) would otherwise win
   on specificity and collapse it back onto one line, so this is written against ".thread-head
   .tagsbar", one class more specific, rather than ".tagsbar" alone. */
.thread-head .tagsbar{display:flex;flex-wrap:wrap;align-items:center;gap:6px;flex-basis:100%;
  margin-top:6px;padding-top:8px;border-top:1px dashed var(--line)}
.tagsbar-label{font-size:11px;color:var(--muted);font-weight:600}
.tagchipform{display:inline-flex;align-items:center;gap:3px;margin:0}
.tagchip-x{font:inherit;font-size:12px;line-height:1;border:0;background:none;color:var(--muted);
  cursor:pointer;padding:0 2px}
.tagchip-x:hover{color:var(--warn)}
.tagaddform{display:inline-flex;align-items:center;gap:4px}
.tagaddform input[type=text]{font:inherit;font-size:12px;padding:5px 9px;border:1px solid var(--line);
  border-radius:8px;background:var(--bg);width:140px}
.tagaddform button{font:inherit;font-size:12px;font-weight:600;padding:5px 12px;border:0;
  border-radius:8px;background:var(--brand);color:#fff;cursor:pointer}
.tagaddform button:hover{background:var(--brand-dk)}

/* ── CRM panel (2026-09-17, compacted same day) ────────────────────────────────────────────
   Charles: "the salesperson will immediately know whether this is a prospect or what, at which
   stage, and maybe with some summary or something like that." Own full-width row, own block below
   the name - so it is the first thing seen after the contact's name, without displacing the role
   dropdown / "Name this person" field / tags bar (see includes/crm-view.php for what this draws).
   flex-basis:100% for the same reason .thread-head .tagsbar already uses it - see that rule's own
   comment for why the selector has to be this specific to win against the mobile media query.

   ⭐ COLLAPSED TO ONE LINE BY DEFAULT. This used to print stage, status line, "Set <date> - <who>",
   note, enquiry and summary each on their own row - ~330px on desktop, ~660px of an 844px mobile
   viewport before the first message. Charles: "leave more space to see the messages". The summary
   <summary class="crmlead-summary"> line carries just the stage pill + money + short date +
   register - see includes/crm-view.php's fm_render_crm_panel(). Everything else lives in
   .crmlead-body, one click away via the native <details> disclosure; nothing was removed. */
.thread-head .crmpanel{flex-basis:100%;margin-top:6px;padding-top:8px;
  border-top:1px dashed var(--line);font-size:13px}
.crmlead{margin-bottom:8px}
.crmlead-summary{cursor:pointer;display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  list-style:none;user-select:none}
.crmlead-summary::-webkit-details-marker{display:none}
.crmlead-summary::after{content:"details";margin-left:auto;font-size:10px;font-weight:600;
  color:var(--muted);text-transform:uppercase;letter-spacing:.3px}
.crmlead[open] .crmlead-summary::after{content:"hide"}
.crmmoney{font-size:13px;font-weight:700;color:var(--ink)}
.crmdate{font-size:12px}
.crmlead-body{margin-top:8px;padding-top:8px;border-top:1px dashed var(--line)}
.crmleadfull{margin-bottom:8px}
.crmleadfull:last-child{margin-bottom:0}
.crmlead-top{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.crmpill{display:inline-block;font-size:11px;font-weight:700;letter-spacing:.2px;
  padding:3px 10px;border-radius:999px;white-space:nowrap}
.crmreg{font-size:11px;color:var(--muted);font-weight:600}
.crmstatus{margin-top:4px;font-weight:600}
.crmmeta{font-size:11px;margin-top:2px}
.crmnote{margin-top:3px}
.crmenquiry{margin-top:3px;color:var(--muted)}
.crmenquiry-label{font-weight:600;color:var(--ink)}
.crmjob{margin-top:6px;padding:6px 10px;background:var(--bg);border-radius:8px;border:1px solid var(--line)}
.crmjob-stage{font-weight:600}
.crmjob-money{font-size:12px;margin-top:2px}
.crmjob-hold{font-size:12px;margin-top:2px;color:var(--warn);font-weight:600}
.crmsummary{margin-top:6px}
.crmsummary-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;
  letter-spacing:.3px}
.crmsummary-text{margin-top:2px}
.crmsummary-stale{color:var(--warn);font-weight:600}

.messages{flex:1;overflow-y:auto;padding:18px 16px;display:flex;flex-direction:column;gap:3px}
.daysep{text-align:center;margin:14px 0 8px}
.daysep span{background:#e7ecea;color:var(--muted);font-size:11px;padding:3px 12px;border-radius:999px}
.msg{display:flex}
.msg.out{justify-content:flex-end}
.bubble{max-width:min(560px,78%);background:var(--in);border:1px solid var(--line);
  border-radius:var(--radius);padding:8px 11px 5px;box-shadow:0 1px 1px rgba(0,0,0,.03)}
.msg.out .bubble{background:var(--out);border-color:#c5e6d5}
.bubble .text{white-space:pre-wrap;word-wrap:break-word;overflow-wrap:anywhere}
.bubble .meta{font-size:10px;color:var(--muted);text-align:right;margin-top:2px}
.media{max-width:100%;border-radius:8px;display:block;margin-bottom:4px;max-height:340px}
.doc{display:inline-block;padding:8px 12px;background:var(--bg);border:1px solid var(--line);
  border-radius:8px;margin-bottom:4px;font-size:14px}
.mediafail{font-size:13px;color:var(--warn);margin-bottom:4px}

/* ── composer ── */
.composer{display:flex;align-items:flex-end;gap:8px;padding:10px 14px;background:var(--card);
  border-top:1px solid var(--line);position:relative;flex-wrap:wrap}
.composer textarea{flex:1;min-height:40px;max-height:160px;resize:vertical;font:inherit;
  padding:9px 12px;border:1px solid var(--line);border-radius:var(--radius);background:var(--bg)}
.composer textarea:focus{outline:2px solid var(--brand);outline-offset:1px}
.composer button{font:inherit;font-weight:600;padding:10px 20px;border:0;border-radius:var(--radius);
  background:var(--brand);color:#fff;cursor:pointer}
.composer button:hover{background:var(--brand-dk)}
.attach{cursor:pointer;font-size:20px;padding:7px 9px;border:1px solid var(--line);
  border-radius:var(--radius);background:var(--bg);line-height:1}
.attach input{display:none}
.attach-name{flex-basis:100%;font-size:12px;color:var(--muted);padding-left:4px}
.attach-name.over{color:var(--warn);font-weight:600}
/* Enter sends. That is a trap worth warning about in writing rather than in a comment: a
   coordinator pressing Enter out of habit mid-sentence fires a half-written reply at a real
   customer, and there is no unsend. Charles, 2026-09-17: "prevent the user from typing in wrongly
   without the full information because of accidental pressing ENTER". Always visible, and it
   brightens while the box has focus - i.e. exactly when they are about to press it. */
.composer-hint{flex-basis:100%;font-size:12px;color:var(--muted);padding-left:4px;
  opacity:.75;transition:opacity .15s ease,color .15s ease}
.composer.focused .composer-hint{opacity:1;color:var(--brand-dk)}
.composer-hint kbd{font:inherit;font-size:11px;font-weight:600;padding:1px 5px;margin:0 1px;
  border:1px solid var(--line);border-bottom-width:2px;border-radius:4px;background:var(--bg)}

/* ── the search box got a real button (2026-09-17) ─────────────────────────────────────────── */
/* It had none and relied on implicit submission, which read as "the search bar is not working".
   margin-left removed 2026-09-17 when the button moved inline with the input into .searchrow -
   that flex row's own `gap` now supplies the spacing; keeping both would double it. */
.filters .searchgo{display:inline-flex;align-items:center;justify-content:center;
  flex:0 0 auto;padding:8px 10px;border:0;border-radius:var(--radius);
  background:var(--brand);color:#fff;cursor:pointer}
.filters .searchgo:hover{background:var(--brand-dk)}
.filters .searchclear{font-size:12px;color:var(--muted);text-decoration:underline;flex:0 0 auto}

/* ── collapsible "More filters" (2026-09-17) ────────────────────────────────────────────────
   Charles: the filter chrome "is taking too much vertical space ... leave more space to see the
   messages and the conversation list". Role chips (Prospects/Customers/Blocked) stay always-visible
   - they are the primary, most-used filter and only ever three chips wide. Stage + tag chips move
   behind a <details> disclosure (no JS needed): closed by default, but index.php opens it with the
   `open` attribute the moment a stage or tag filter is actually selected, so an active filter is
   never hidden from the person who set it - only the BROWSING state is collapsed, not the state
   itself. */
.morefilters{margin-top:10px}
.morefilters summary{cursor:pointer;font-size:12px;font-weight:600;color:var(--muted);
  list-style:none;display:inline-flex;align-items:center;gap:6px;padding:2px 0;user-select:none}
.morefilters summary::-webkit-details-marker{display:none}
.morefilters summary::before{content:"+";display:inline-block;width:12px;text-align:center;
  font-weight:700}
.morefilters[open] summary::before{content:"\2212"}
.morefilters summary:hover{color:var(--ink)}
.morefilters .filtercount{background:var(--brand-lt);color:var(--brand-dk);font-size:11px;
  font-weight:700;padding:1px 7px;border-radius:999px}
.morefilters .chips{margin-top:8px}

/* ⛔ The composer, when focus arrived on its OWN (type-anywhere) rather than from a click. It has
   to be unmistakable: the coordinator may believe they are typing into the search box, and the
   next Enter would otherwise send their search term to a real customer. See index.php. */
.composer.autofocused textarea{border-color:var(--brand);box-shadow:0 0 0 3px rgba(16,122,88,.16)}
.composer.autofocused .composer-hint::after{content:" — typing to this customer. Esc to stop.";
  color:var(--warn);font-weight:600}
@keyframes fmflash{0%,100%{box-shadow:0 0 0 3px rgba(16,122,88,0)}45%{box-shadow:0 0 0 4px rgba(200,110,20,.5)}}
.composer.confirm-flash textarea{animation:fmflash .7s ease}
.composer.confirm-flash .composer-hint::after{content:" — press Enter again to send";
  color:var(--warn);font-weight:600}

/* ── who replied, and delete-for-everyone (both 2026-09-17) ────────────────────────────────── */
/* The name of the staff member who sent an outbound reply. Charles: "I want to capture
   internally who has replied to the customer/prospect." Quiet by design - it is an audit
   detail, not a headline, and it sits inline with the timestamp. */
.msg .meta .sentby{font-weight:600}
/* Delete appears only on our own messages still inside WhatsApp's delete-for-everyone window,
   and only on hover/focus so it cannot be fat-fingered while scrolling a thread on a phone.
   ⚠️ On touch there is no hover, so it is revealed by tapping the bubble - see index.php. */
.msg .meta .msgdel{font:inherit;font-size:11px;font-weight:600;margin-left:8px;padding:0 4px;
  border:0;border-radius:4px;background:none;color:var(--warn);cursor:pointer;
  opacity:0;transition:opacity .12s ease}
.msg:hover .meta .msgdel,.msg.show-actions .meta .msgdel,.msg .meta .msgdel:focus{opacity:1}
.msg .meta .msgdel:hover{text-decoration:underline}
/* A deleted message keeps its place in the thread rather than vanishing - a row that silently
   disappears reads as the delete having failed. Mirrors WhatsApp's own tombstone. */
.msg.revoked .bubble{background:transparent;border:1px dashed var(--line);box-shadow:none}
.msg.revoked .text.deleted{color:var(--muted);font-style:italic}

.empty{padding:28px 16px;text-align:center;color:var(--muted);font-size:14px}

/* ── toast ── */
.toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%);padding:10px 20px;
  border-radius:999px;color:#fff;font-size:14px;z-index:50;box-shadow:0 4px 14px rgba(0,0,0,.18)}
.toast.ok{background:var(--brand-dk)}
.toast.err{background:#8c2f14}

/* ── login ── */
.loginpage{display:flex;align-items:center;justify-content:center;background:var(--brand-lt)}
.loginbox{background:var(--card);padding:32px;border-radius:16px;width:min(370px,92vw);
  box-shadow:0 8px 30px rgba(15,138,106,.14)}
.loginbox h1{margin:0;font-size:22px}
.loginbox .sub{margin:4px 0 20px;font-size:13px}
.loginbox label{display:block;font-size:13px;font-weight:600;margin-bottom:14px;color:var(--muted)}
.loginbox input{display:block;width:100%;margin-top:5px;padding:10px 12px;font:inherit;
  border:1px solid var(--line);border-radius:9px;background:var(--bg);color:var(--ink)}
.loginbox input:focus{outline:2px solid var(--brand);outline-offset:1px}
.loginbox button{width:100%;padding:11px;font:inherit;font-weight:600;border:0;border-radius:9px;
  background:var(--brand);color:#fff;cursor:pointer;margin-top:4px}
.loginbox button:hover{background:var(--brand-dk)}
.loginbox .err{background:#fdeceb;color:#8c2f14;padding:9px 12px;border-radius:8px;
  font-size:13px;margin-bottom:14px}

/* ── bare User ID login (2026-08-27, relabelled 2026-09-17): no title, no heading, no phone
   number — just the words "User ID" and the box.
   ⚠️ .pin-input is GONE, replaced by .userid-input. Its 28px font and 14px letter-spacing were
   sized for exactly four characters; an email address under those rules overflows the card.
   Nothing else in the tree referenced .pin-input (checked), so it was removed rather than left
   as dead weight for a future editor to puzzle over. */
.pinbox{padding:40px 32px}
.userid-label{display:block;font-size:13px;font-weight:600;margin-bottom:8px;color:var(--muted)}
.userid-input{display:block;width:100%;padding:14px 12px;font:inherit;font-size:17px;
  text-align:center;border:1px solid var(--line);border-radius:9px;background:var(--bg);color:var(--ink)}
.userid-input:focus{outline:2px solid var(--brand);outline-offset:1px}
.waiting{text-align:center;color:var(--muted);font-size:14px;padding:12px 0}
.waiting .dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--brand);
  margin:0 2px;animation:pulse 1.2s ease-in-out infinite}
.waiting .dot:nth-child(2){animation-delay:.2s}
.waiting .dot:nth-child(3){animation-delay:.4s}
@keyframes pulse{0%,80%,100%{opacity:.25}40%{opacity:1}}

/* ── phone: one pane at a time ──
   ⚠️ Every flex child here carries min-width:0. Without it a flex item refuses to shrink below
   its content width, which is what pushed the whole thread wider than a 390px viewport and cut
   the right-hand edge off every bubble and timestamp. */
@media (max-width:820px){
  .layout{grid-template-columns:1fr}
  .thread{display:none}
  .layout.has-thread .list{display:none}
  .layout.has-thread .thread{display:flex}
  .back{display:block}

  .topbar{gap:8px;padding:0 10px}
  .brand{font-size:15px;white-space:nowrap}
  .wa-status{font-size:11px;padding:2px 8px;white-space:nowrap;overflow:hidden;
    text-overflow:ellipsis;max-width:44vw}
  .who{font-size:0}                 /* the name is noise on a phone; keep the button */
  .logout{font-size:12px;margin-left:0}

  /* The role editor drops to its own full-width row instead of stretching the header. */
  .thread-head{flex-wrap:wrap;gap:8px;padding:10px 12px}
  .thread-head > div{flex:1 1 auto;min-width:0}
  .t-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .t-sub{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .thread-head .crmpanel{order:3;width:100%}
  .roleform{order:4;width:100%;margin:0;flex-wrap:nowrap}
  .roleform input{flex:1 1 auto;min-width:0;width:auto}
  .roleform select,.roleform button{flex:0 0 auto}
  .thread-head .tagsbar{order:5;width:100%}

  .messages{padding:14px 10px}
  .msg{min-width:0}
  .bubble{max-width:86%;min-width:0}
  .composer{padding:8px 10px}

  /* ── the left column had the SAME "grid item won't shrink below its content" bug the comment
     above already fixed for .thread/.messages, just never applied here (found 2026-09-17 while
     measuring the filter/list density fix): a long, unbroken conversation-preview string forced
     .list - a grid item of .layout - up to ~560px wide inside a 390px viewport, pushing content
     off-screen. Same fix, same reasoning: min-width:0 down the chain lets it actually shrink to
     the track. Measured before/after: .list 560.7px -> 390px, first .convos row's own
     .name/.prev then truncate with ellipsis exactly as their CSS already intends. */
  .list{min-width:0}
  .convos li a{min-width:0}
  .row1,.row2{min-width:0}
  .name,.prev{min-width:0}
}

/* ── phone header (2026-09-17) ──────────────────────────────────────────────────────────────
   Charles: "the header menu is not positioned properly in mobile view." At 390px the topbar's own
   flex row does fit (.topbar itself measured 390px wide, no page-level overflow) - the break was
   .who's own box being squeezed too narrow for "Sign out" to sit on one line once wrapping was
   prevented (see .logout above), so it wrapped onto a second line, taller than the topbar could
   show, and got clipped. Fix: free up room by shortening the two things that can afford it - the
   " Inbox" wordmark (redundant once you're already on the page) and the WhatsApp status pill,
   which already truncates with an ellipsis so shortening its cap loses no information the pill
   didn't already have room to show. Measured with the droplet's real Chrome at 390px: brand 134px
   -> 77px, wa-status capped at 72px, .who's two links then sit on one line (top/bottom identical
   for both), nothing wraps, nothing clips. */
@media (max-width:480px){
  .topbar{gap:6px;padding:0 8px}
  .brand{font-size:13px}
  .brand .muted{display:none}
  .wa-status{max-width:72px;padding:2px 6px;font-size:10px}
  .who{display:flex;align-items:center;gap:6px;flex:0 0 auto}
  .logout{padding:4px 7px;font-size:11px;margin-left:0}
}

/* Screen-reader-only label for the icon-only attach control. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0}
.attach{display:inline-flex;align-items:center;justify-content:center;color:var(--muted)}
.attach:hover{color:var(--brand);border-color:var(--brand)}

/* -- leads.php - recovered "old lead" context (2026-09-17) -----------------------------------
   Charles: "for the old 27 only, give more details on the chats but visible internally... so that
   we can whatsapp them again if we want to follow up." A plain, full-width working list, not a
   split-pane inbox - these leads have no conversation to open, so there is no thread pane to show. */
.leadswrap{max-width:920px;margin:0 auto;padding:18px 16px 40px}
.leadsintro{margin:0 0 14px;font-size:13px;max-width:640px}
.leadfilters{margin-bottom:18px}
.leadfilters .chips{margin-top:8px}
.leadfilters .chips:first-child{margin-top:0}
.leadclear{display:inline-block;margin-top:8px;font-size:12px;color:var(--muted);text-decoration:underline}

/* One extra stage bucket leads.php adds on top of crm-view.php's own FM_CRM_STAGE_SLUGS: a
   register row already marked excluded:true (today: Alpha-Custsrv, internal staff misfiled as a
   lead). Deliberately a colour none of the other 9 stage/confidence pills use, so "this is not
   actually a customer" reads as its own category at a glance, not a shade of any existing one. */
.stage-excluded{background:#efe3fb;color:#5a2f96}

.leadcard{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  padding:14px 16px;margin-bottom:14px}
/* A 'none'-confidence card must look visibly different from a 'high' one on sight, not just in
   its text - dashed border + muted background, same visual language .stage-no-record already
   uses elsewhere in this app for "nothing here", so it reads as an honest empty state, not a
   rendering glitch. */
.leadcard.conf-none{border-style:dashed;background:var(--bg)}
.leadtop{display:flex;flex-wrap:wrap;align-items:center;gap:8px;justify-content:space-between}
.leadname{font-weight:700;font-size:16px}
.leadreg{margin-left:8px;font-size:11px;font-weight:700;text-transform:uppercase;
  letter-spacing:.3px;color:var(--muted)}
.leadphone-row{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:8px}
.leadphone{font-variant-numeric:tabular-nums;font-size:14px;user-select:all}
.wa-link{display:inline-flex;align-items:center;gap:5px;padding:5px 13px;border-radius:999px;
  background:var(--brand);color:#fff;font-size:12px;font-weight:600}
.wa-link:hover{background:var(--brand-dk)}
.leadmeta{font-size:12px;color:var(--muted);margin-top:8px}
.conf-pill{display:inline-block;font-size:11px;font-weight:700;padding:2px 9px;border-radius:999px}
.conf-high{background:var(--brand-lt);color:var(--brand-dk)}
.conf-medium{background:#fff4de;color:#8a6a1a}
.conf-low{background:#fdeceb;color:#8c2f14}
.conf-none{background:#f1f3f2;color:var(--muted);border:1px dashed var(--line)}
/* The "internal, reconstructed from staff chat" label - reuses the same amber/dashed treatment
   the tag filter already uses for .tagchip-ai (assets/style.css, "AI-vs-human provenance"), on
   purpose: it is the same kind of warning (AI/derived, not a human's or the customer's own words)
   and should look like the one the coordinator already recognises from the inbox. */
.leadctx-label{display:inline-block;margin-top:12px;font-size:11px;font-weight:800;
  letter-spacing:.3px;text-transform:uppercase;color:#8a6d1a;background:#fff8e6;
  padding:3px 9px;border-radius:6px;border:1px dashed #d8b74a}
.leadctx-body{margin-top:6px;font-size:14px;line-height:1.5}
.leadctx-body.empty{color:var(--muted);font-style:italic}

@media (max-width:820px){
  .leadswrap{padding:14px 12px 32px}
  .leadtop{gap:6px}
  .leadphone-row{gap:8px}
}

/* ── pair-relay.php (2026-09-18) ────────────────────────────────────────────────────────────
   Charles Lau: "work on the second route" - relaying a WhatsApp companion-device pairing code
   so a salesperson does not have to be in the same room as whoever holds the primary handset.
   Same page-shell pattern as .leadswrap (a plain internal page, not the two-pane inbox layout). */
.relaywrap{max-width:640px;margin:0 auto;padding:20px 16px 40px}
.relaynote{background:var(--brand-lt);border:1px solid var(--brand);border-radius:var(--radius);
  padding:14px 16px;font-size:13px;line-height:1.55;margin-bottom:16px}
.relaysteps{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  padding:14px 16px 14px 34px;font-size:13px;line-height:1.6;margin:0 0 16px}
.relaysteps li{margin-bottom:6px}
.relaysteps li:last-child{margin-bottom:0}
/* Same amber/warning language the app already uses for something that must not be glossed over -
   see .leadctx-label's own comment for the precedent this borrows from. */
.relaywarn{background:#fff8e6;border:1px dashed #d8b74a;border-radius:var(--radius);
  padding:14px 16px;font-size:13px;line-height:1.55;margin-bottom:20px;color:#5a4614}
.relaywarn strong{color:#8a6d1a}
.relaywarn ul{margin:8px 0 0;padding-left:20px}
.relaywarn li{margin-bottom:6px}
.relaywarn li:last-child{margin-bottom:0}

.relayform{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  padding:18px 18px 20px;display:flex;flex-direction:column;gap:14px}
.relayfield{display:flex;flex-direction:column;gap:5px;font-size:13px;font-weight:600}
.relayfield .muted{font-weight:400}
.relayfield select,.relayfield input[type=text]{font:inherit;font-size:14px;padding:9px 11px;
  border:1px solid var(--line);border-radius:8px;background:var(--bg);color:var(--ink)}
.relayfield select:focus,.relayfield input[type=text]:focus{outline:2px solid var(--brand);outline-offset:1px}
/* Monospace + letter-spacing for the code field only - a pairing code is meant to be read
   character-by-character, and the extra spacing makes a mistyped I/1 or O/0 easier to spot before
   it goes out, even though the strict allowlist below (fm_relay_normalize_code) will reject one
   from WhatsApp's own alphabet regardless. */
.relayfield input[name=code]{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  letter-spacing:2px;text-transform:uppercase}
.relayform button{align-self:flex-start;font:inherit;font-weight:600;font-size:14px;padding:10px 22px;
  border:0;border-radius:8px;background:var(--brand);color:#fff;cursor:pointer}
.relayform button:hover{background:var(--brand-dk)}

@media (max-width:640px){
  .relaywrap{padding:14px 12px 32px}
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   THREAD HEADER REDESIGN + TRADE TABS (2026-09-18) - added at end of file on purpose, this
   project's own block, kept separate from concurrent edits elsewhere in this file. Charles:
   "the design shows duplicated 'Not contacted'. can you design the wireframe smoother on this and
   the name this prospect textfield on the right?" plus, mid-build: put Wire Mesh / Window Repair
   up as top-level tabs beside "WhatsApp connected".

   These rules OVERRIDE a few older ones by using a MORE SPECIFIC selector rather than editing the
   originals in place (".thread-head .crmpanel" -> ".thread-head .thread-strip .crmpanel" etc.) -
   cascade + specificity both favour the new rule, so the old ones stay untouched for whatever else
   might still read them, and this diff never touches a line above this block.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* ── trade tabs, in the topbar beside "WhatsApp connected" ── */
.tradetabs{display:flex;align-items:center;gap:4px;flex-wrap:wrap}
.tradetab{font-size:12px;font-weight:600;padding:5px 12px;border-radius:999px;
  background:rgba(255,255,255,.14);color:#eafaf3;white-space:nowrap}
.tradetab:hover{background:rgba(255,255,255,.24)}
.tradetab.on{background:#fff;color:var(--brand-dk)}
.tradetab-unassigned{font-size:11px;font-weight:700;padding:4px 9px;border-radius:999px;
  background:#fff4de;color:#8a6a1a;white-space:nowrap}

/* ── thread-head row 1: back + name (click-to-edit) + role + Save, one row ── */
.thread-head-top{display:flex;align-items:center;gap:10px;flex-basis:100%;min-width:0}
/* Overrides the base .roleform{margin-left:auto} rule (it now lives INSIDE this row, not beside a
   separate name <div> as it used to) - higher specificity wins regardless of source order, but
   this sits after it in the file too. */
.thread-head-top .roleform{margin-left:0;flex:1 1 auto;min-width:0}
.t-nameedit{position:relative;flex:1 1 auto;min-width:0;display:flex;align-items:center}
.t-name-display{font:inherit;display:inline-flex;align-items:center;gap:6px;max-width:100%;
  padding:4px 6px;margin:-4px -6px;border:0;border-radius:8px;background:none;cursor:pointer;
  text-align:left;color:inherit}
.t-name-display:hover{background:var(--bg)}
.t-name-display:hover .t-name-pencil{opacity:1}
.t-name-display .t-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.t-name-pencil{font-size:12px;opacity:.45;flex:0 0 auto}
.t-name-input{font:inherit;font-weight:650;font-size:15px;padding:5px 8px;
  border:1px solid var(--brand);border-radius:8px;background:var(--bg);width:100%;max-width:260px}
.t-name-input:focus{outline:2px solid var(--brand);outline-offset:1px}

/* ── the strip: CRM stage + "Appears in" + "Tags", flowing together instead of stacked rows ── */
.thread-head .thread-strip{flex-basis:100%;display:flex;flex-wrap:wrap;align-items:center;
  gap:6px 14px;margin-top:6px;padding-top:8px;border-top:1px dashed var(--line)}
/* Cancels the older full-row treatment (own top border/margin, flex-basis:100%) now that these
   sit INSIDE .thread-strip, which already carries one shared border/margin for the whole group. */
.thread-head .thread-strip .crmpanel{flex-basis:auto;margin-top:0;padding-top:0;border-top:0}
.thread-head .thread-strip .tagsbar{flex-basis:auto;margin-top:0;padding-top:0;border-top:0}
.thread-head .thread-strip .crmlead{margin-bottom:0}
/* The stage pill's own "details"/"hide" toggle stays exactly where fm_render_crm_panel() puts it
   (end of its own <summary>) - it no longer needs to also govern the whole strip's layout, so it
   keeps working exactly as it did before this file existed. */

.appearsin{display:inline-flex;flex-wrap:wrap;align-items:center;gap:6px}
.appearsin-label{font-size:11px;color:var(--muted);font-weight:600}
.appearschip{background:var(--brand-lt);color:var(--brand-dk);border:1px solid var(--brand)}
.appearsin .tagchipform{display:inline-flex;align-items:center;gap:3px;margin:0}
.appearsin-add{font:inherit;font-size:11px;font-weight:600;padding:3px 10px;border-radius:999px;
  border:1px dashed var(--brand);background:none;color:var(--brand-dk);cursor:pointer}
.appearsin-add:hover{background:var(--brand-lt)}

/* t-sub (phone / push name) is now a direct child of .thread-head, not nested beside .t-name -
   force it onto its own full row the same way .thread-strip already is. */
.thread-head > .t-sub{flex-basis:100%;margin-top:2px}

@media (max-width:820px){
  /* Same reasoning as the desktop rules above: override via a longer selector rather than editing
     the original .thread-head > div / .roleform / .thread-head .crmpanel / .thread-head .tagsbar
     mobile rules, which targeted a DOM shape this redesign no longer produces (name/role were
     siblings of a wrapper div before; crmpanel/tagsbar were direct children of .thread-head before -
     both are now nested one level deeper, inside .thread-head-top / .thread-strip). */
  .thread-head-top{gap:6px}
  .thread-head-top .roleform{flex-wrap:nowrap;gap:6px}
  .t-nameedit{min-width:0}
  .t-name-input{max-width:none}
  .thread-head .thread-strip{order:initial;width:100%}

  /* ⛔ FIX (found by this project's own screenshot test, 2026-09-18): the base .topbar has no
     flex-wrap, so adding .tradetabs here pushed the row past 390px and clipped "Wire Mesh" /
     "Window Repair" instead of moving to a new line - measured clientWidth 390 vs actual
     scrollWidth 449px before this fix. .topbar must be allowed to wrap, and .tradetabs (last in
     source order among the topbar's own children, width:100%) is what then drops to its own row
     below brand/wa-status/who, which stay together on row 1 exactly as they did before tabs
     existed. */
  .topbar{flex-wrap:wrap;height:auto;min-height:56px;row-gap:6px}
  .tradetabs{order:5;width:100%;justify-content:flex-start;margin-top:2px}
}

@media (max-width:480px){
  .tradetab{padding:4px 9px;font-size:11px}
  .tradetab-unassigned{font-size:10px}
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   MOBILE PASS, ROUND 2 (2026-09-18) - Charles via the coordinator, verbatim: "roughly 70% of the
   screen is chrome before a single word of the conversation... leave more space to see the
   messages" - measured 8 header rows on a 390x844 phone, target: first message bubble above
   380px. Desktop (~260px to first bubble, unchanged by this block) must not regress - every rule
   below is scoped to a max-width media query or a selector that only matches the NEW mobile-only
   markup (.navmenu / .tagsdetails toggles), so desktop keeps exactly the layout already verified.

   THE ONE TECHNIQUE USED THROUGHOUT: wrap secondary controls in a plain <details> (no JS, same
   pattern this app already uses for .morefilters and the CRM panel itself), then FORCE it open on
   desktop by overriding the browser's native "hidden unless [open]" behaviour with author CSS
   (display:block/flex on the content, display:none on the toggle) - and do nothing extra on
   mobile, where the browser's own native collapse/expand already does the job. Nothing behind a
   details here is REMOVED, only reached with one extra tap on a narrow screen: Leads / Link a
   device / Sign out (.navmenu) and the general "Tags:" chips + add box (.tagsdetails) - Appears-in
   (the two trade tags) stays always visible everywhere, per Charles's own reasoning that it IS the
   tag control for the two trades and does not need a second, redundant "Tags:" label next to it.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* Full/short label pairs (tabs, the two trade quick-add buttons, the unassigned badge) - default
   (desktop and tablet) shows the full word; only the narrowest phones swap to the short one. */
.tab-short{display:none}

/* ── nav menu: Leads / Link a device / Sign out behind one toggle on the narrowest phones ──
   ⛔ CSS-ONLY "FORCE OPEN" DOES NOT WORK HERE, measured three ways against this box's actual
   Chrome (148.0.7778.96) before giving up on it: `display:block` on the child - whoOffsetWidth=0;
   adding `display:contents` on the <details> itself too - still 0; adding
   `content-visibility:visible` (Chrome's real collapsed-<details> mechanism, confirmed via
   getComputedStyle - NOT display:none) on top of both - STILL 0, with getComputedStyle reporting
   display:block AND contentVisibility:visible while offsetWidth stayed 0 regardless. Isolated with
   a direct experiment: calling `.setAttribute('open','')` via JS on the exact same element, same
   CSS, immediately produced a correct non-zero width. Conclusion: this Chrome version's flex
   auto-width computation for a <details> box depends on the actual `open` ATTRIBUTE, not on any
   CSS override of how its content renders - a genuine engine quirk, not a mistake in the CSS
   above. Since CSS cannot set an HTML attribute, this is handled in the script block at the bottom
   of index.php instead: on load (and on resize), JS sets `open` on `.navmenu`/`.tagsdetails` when
   the viewport is wider than 480px, and removes it otherwise.
   ⛔ THE TOGGLE ITSELF STAYS VISIBLE AT EVERY WIDTH, ON PURPOSE - it is NOT hidden at desktop
   width the way an earlier draft of this rule did. That draft made desktop reachability depend
   entirely on JS running (JS opens it; CSS alone hides the only way to open it without JS) - a
   real, if narrow, way to lose "must be reachable" that this project's own house rule ("never let
   a secret/control become unreachable") caught before shipping. With the toggle always visible,
   reachability holds with JS on OR off, at every width - JS only saves a tap on desktop, it is
   never required for reachability. Applied to .tagsdetails below too - identical bug, identical
   fix, identical reasoning. */
.navmenu-toggle{font-size:14px;color:#eafaf3;opacity:.85;cursor:pointer;list-style:none;
  padding:3px 8px;border-radius:6px}
.navmenu-toggle:hover{opacity:1;background:rgba(255,255,255,.12)}
.navmenu-toggle::-webkit-details-marker{display:none}
.navmenu-toggle::after{content:" Menu";font-size:11px}   /* the icon itself is the element's own
  text content (&#8942; in the markup) - this just labels it at desktop width, where there is room */
/* Once JS has auto-opened it on a wide screen, the toggle and the now-expanded content would both
   show at once - redundant chrome for no reason, since there is nothing to tap it FOR at this
   width (crossing back below the breakpoint auto-closes it again, see the script's own comment).
   Kept visible at mobile widths even when open - see the media query below - because there the
   toggle IS how a coordinator closes it again. */
.navmenu[open] > .navmenu-toggle{display:none}

/* ── general "Tags:" behind one toggle on the narrowest phones - Appears-in is unaffected ── */
.tagsdetails-summary{display:inline-flex;font-size:11px;color:var(--muted);font-weight:600;
  cursor:pointer;list-style:none;align-items:center;gap:3px;padding:2px 0}
.tagsdetails-summary::-webkit-details-marker{display:none}
.tagsdetails[open] > .tagsdetails-summary{display:none}   /* same reasoning as .navmenu above */

@media (max-width:480px){
  /* Tabs/topbar try to share ONE row instead of dropping to their own (overrides round 1's
     order:5/width:100% specifically at this narrower breakpoint only - tablet, 481-820px, keeps
     the safer own-row behaviour from round 1 untouched). */
  .topbar{gap:4px;padding:0 6px}
  .tradetabs{order:initial;width:auto;margin-top:0;flex-shrink:1;min-width:0;gap:3px}
  .tradetab{padding:4px 7px;font-size:10.5px}
  .tab-full{display:none}
  .tab-short{display:inline}
  .tradetab-unassigned{padding:3px 6px}
  .wa-status{max-width:26px;padding:2px 4px;font-size:0}   /* dot-only: the colour (ok/bad) still
    carries the signal at a glance; the full sentence is one tap away via .who if truly needed, and
    the title="WhatsApp connection" attr + JS-set title text still carry it for a11y. */

  /* The menu toggle itself - small, round, same visual language as a tab pill. Needs to be a real
     positioned box here (the popover's anchor) - it already is one at every width, this just adds
     the anchor. */
  .navmenu{position:relative}
  .navmenu-toggle{display:inline-flex;align-items:center;justify-content:center;
    width:26px;height:26px;border-radius:999px;background:rgba(255,255,255,.14);
    color:#fff;cursor:pointer;font-size:16px;list-style:none;padding:0}
  .navmenu-toggle::-webkit-details-marker{display:none}
  .navmenu-toggle::after{content:""}                /* icon-only in the round button - no room for "Menu" */
  .navmenu[open] > .navmenu-toggle{display:inline-flex}   /* re-shown here: at THIS width the toggle is how a coordinator closes it again, unlike the auto-opened desktop case above */
  .navmenu > .who{display:none}                    /* JS did not open it (narrow viewport) - native collapsed-by-default */
  .navmenu[open] > .who{display:flex;flex-direction:column;align-items:flex-start;gap:8px;
    position:absolute;right:0;top:100%;margin-top:8px;padding:10px 12px;min-width:150px;
    background:#fff;border-radius:10px;box-shadow:0 4px 16px rgba(0,0,0,.22);z-index:30;
    font-size:13px;color:var(--ink)}
  .navmenu[open] > .who .logout{border-color:var(--line);color:var(--brand-dk);margin:0}

  /* Row 5 (phone / push name) - Charles via the coordinator: "near-zero value when the name is
     right above it... consider dropping it below a breakpoint." Dropped outright, as offered -
     nothing in the "must not lose" list below names the phone/push-name display. */
  .thread-head > .t-sub{display:none}

  .appearsin-label{display:none}                   /* redundant once "Tags:" carries its own toggle label instead - see .tagsdetails-summary below */
  .tagsdetails-summary{display:inline-flex;font-size:11px;color:var(--muted);font-weight:600;
    cursor:pointer;list-style:none;align-items:center;gap:3px;padding:2px 0}
  .tagsdetails-summary::-webkit-details-marker{display:none}
  .tagsdetails-summary::before{content:"+";font-weight:700}
  .tagsdetails[open] .tagsdetails-summary::before{content:"-"}
  .tagsdetails[open] > .tagsdetails-summary{display:inline-flex}   /* re-shown here (same reasoning as .navmenu[open] > .navmenu-toggle above): at THIS width it is how a coordinator closes it again */
  .tagsdetails-count{opacity:.7;font-weight:500}
  .tagsdetails[open] > .tagsbar{display:flex;margin-top:6px}
  .tagsdetails .tagsbar-label{display:none}         /* the outer summary already says "Tags" */

  .thread-head-top{gap:4px}
  .thread-head .thread-strip{gap:4px 8px;margin-top:4px;padding-top:6px}
  .appearschip,.appearsin-add{font-size:10.5px;padding:3px 8px}
}
