  /* ---------- Mail module (#627, #781): the styles moved out of the Inbox page ----------
     These rules moved verbatim from `src/inbox/app.html` into this file (#781). They are the Mail
     module's own styles: the thread reader and its messages, the AI held draft and internal note,
     the reply bar with its rich-text editor, recipients, formatting and files, the comments drawer
     content with its mentions and shares, the sender CRM context and history, the compose overlay,
     the toast, and the Mail stage and reader wiring. The Mentions module's one inline rule moves
     here too, since only Mail uses it. The shell keeps the shared design system, the tokens, the
     shell grid and dock, the rail and account menu, the context-column track the other modules
     share, the module-scoping rules and the responsive block. No selector here starts with the
     document-root pseudo-class, so none had to stay behind for the inline token block (#778 lesson).
     The module stylesheet loads after the canvas sheets and before the inline <style>, so the
     canvas base is still overridden and the inline shell rules still win a same-specificity tie. */


  /* The pre-port reader slide-over styles (.reader, .reader-scrim, .rd-tabs/.rd-tab/.rd-panel and the
     rest) are gone (#578): the reader is the canvas .ml-rdr inside the Mail stage now, and nothing in
     the DOM carries those classes. The canvas reader rules sit in the Mail module block below. */
  /* The comments drawer is the canvas .ml-cd (mail.css): the header, body and footer, the single-line
     .ml-cbox with the round .send button, the .ml-ac mention menu, the .ml-cm comment cards with an
     avatar and the @ highlight, the .ml-shared people list and the .ml-invite confirmation. The
     class `comment-drawer` stays on the aside, so the reduced-motion rule below still names it, but
     all its layout is the canvas .ml-cd now (#578). These rules carry the comment-card content and
     the live/status off-screen regions the a11y tests read. */
  .comment-list{display:flex;flex-direction:column;gap:14px}
  .ml-cm{display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px}
  .comment-card.ml-cm{position:static;padding:0;border:0;background:none;border-radius:0}
  .ml-cm .comment-meta{display:flex;align-items:baseline;gap:8px;margin:0;font-size:13px}
  .ml-cm .comment-meta .who{font-weight:600;color:var(--ink)}
  .ml-cm .comment-meta .when{font-size:11.5px;color:var(--ink-3);white-space:nowrap;font-variant-numeric:tabular-nums}
  .ml-cm .comment-body{margin:3px 0 0;font-size:13.5px;line-height:1.5;color:var(--ink);white-space:pre-wrap;overflow-wrap:anywhere}
  .ml-cm .comment-body .at{font-weight:600;color:var(--ink);background:var(--sel-2);border-radius:5px;padding:0 3px}
  .ml-cm .comment-actions{display:flex;gap:2px;margin-top:4px}
  .ml-cm .comment-actions button{font-size:12px;font-weight:600;color:var(--ink-3);padding:3px 6px;border-radius:6px}
  .ml-cm .comment-actions button:hover{background:var(--sel);color:var(--ink)}
  .comment-empty{font-size:13px;color:var(--ink-3);padding:4px 2px}
  .comment-mentions{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
  .comment-mention-chip{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:999px;background:var(--raised);border:1px solid var(--line-soft);font-size:12px}
  .comment-mention-chip button{font-size:12px;color:var(--ink-3)}
  .ml-cbox .txt{border:0;background:none;outline:none;resize:none;font:inherit;line-height:1.5;color:var(--ink);padding:0;max-height:120px;overflow-y:auto}
  .ml-cbox .txt::placeholder{color:var(--ink-3)}
  .ml-cbox .txt:disabled{color:var(--ink-faint)}
  .comment-hint{margin:0}
  .comment-live,.comment-status{
    position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
    clip:rect(0,0,0,0);white-space:nowrap;border:0
  }

  .metachip{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--ink-2);font-weight:500}
  .metachip .ico-xs{color:var(--ink-3)}
  .metachip .divdot{width:3px;height:3px;border-radius:50%;background:var(--ink-faint);margin:0 2px}

  /* quiet AI status strip */
  .aibar{display:flex;align-items:flex-start;gap:10px;margin:14px 20px 4px;padding:11px 13px;border-radius:var(--r-md);
    background:var(--ai-bg);border:1px solid var(--ai-line)}
  .aibar .spark{color:var(--ai-ink);margin-top:1px}
  .aibar .txt{font-size:12.5px;color:var(--ai-ink);line-height:1.5}
  .aibar .txt b{font-weight:600}

  .thread{flex:1;overflow-y:auto;overflow-x:hidden;min-height:0;padding:12px 20px 18px;
    display:flex;flex-direction:column;gap:12px;overscroll-behavior:contain}
  .thread > *{flex:none}
  .tstamp{align-self:center;font-size:11px;color:var(--ink-3);font-weight:500;letter-spacing:.04em;padding:6px 0 0}

  .msg{max-width:78%;display:flex;flex-direction:column;gap:4px}
  .msg .meta{display:flex;align-items:center;gap:7px;font-size:11.5px;color:var(--ink-3);padding:0 3px}
  .msg .meta b{color:var(--ink-2);font-weight:600}
  .msg .chan{display:inline-flex;align-items:center;gap:4px}
  .msg.them{align-self:flex-start}
  .msg.you{align-self:flex-end;align-items:flex-end}
  .bub{padding:10px 13px;border-radius:16px;font-size:13.5px;line-height:1.55;white-space:pre-wrap;overflow-wrap:anywhere;
    font-family:"Inter","Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",system-ui,sans-serif}
  .msg.them .bub{background:var(--sel);border-bottom-left-radius:5px}
  .msg.you .bub{background:var(--sel-2);border-bottom-right-radius:5px}
  .bub p{margin:0 0 8px} .bub p:last-child{margin:0}
  .bub-link{color:var(--brand);text-decoration:underline;text-underline-offset:2px;word-break:break-word}
  .bub-img{display:block;max-width:min(100%,280px);max-height:220px;object-fit:contain;border-radius:10px;margin:8px 0;background:var(--sel)}
  .msg .files{display:flex;flex-direction:column;gap:6px;margin-top:6px}
  .msg.you .files{align-items:flex-end}

  /* WhatsApp reply delivery state (#940). The words carry the meaning; the icon and colour only
     reinforce it, so the state never reads by colour alone. */
  .wa-state{display:inline-flex;align-items:center;gap:3px;color:var(--ink-3)}
  .wa-state .ico-xs{width:13px;height:13px}
  .wa-state.is-read{color:var(--brand)}
  .wa-state.is-failed{color:var(--danger)}
  .wa-err{align-self:flex-end;max-width:100%;font-size:11.5px;color:var(--danger);padding:2px 3px 0;text-align:right}
  /* A reaction on the Message it reacts to (#940). */
  .wa-rx{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px;padding:0 3px}
  .msg.you .wa-rx{justify-content:flex-end}
  .wa-rx-chip{font-size:13px;line-height:1.2;background:var(--sel);border:1px solid var(--line);border-radius:999px;padding:3px 7px}
  .attach{display:inline-flex;align-items:center;gap:8px;padding:8px 11px;border:1px solid var(--line);border-radius:9px;font-size:12.5px;color:var(--ink-2);background:var(--canvas);text-decoration:none}
  .attach .ico-sm{color:var(--ink-3)}

  /* WhatsApp media in the thread (#943): a photo or sticker inline, a voice note or audio in a
     labelled player, a document as an "Open" link, and a plain note for a file that is not stored. */
  .wa-media{margin:8px 0}
  video.wa-media{width:min(100%,280px)}
  /* A voice note holds its player and its transcript as siblings in one box, so a player load error
     that replaces the player never removes the transcript (#944). The box carries the outer spacing;
     the player drops its own margin so the transcript's top margin sets the gap below the player. */
  .wa-voice{margin:8px 0}
  .wa-voice .wa-audio{margin:0}
  .wa-audio{display:flex;flex-direction:column;gap:5px;margin:8px 0}
  .wa-audio-h{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;color:var(--ink-3)}
  .wa-audio-h .ico-sm{width:14px;height:14px}
  .wa-audio-el{max-width:min(100%,280px);height:34px}
  .wa-media-doc{margin:8px 0}
  .wa-media-note{margin:6px 0 0;font-size:12px;color:var(--ink-3);font-style:italic}

  /* A voice note's transcript below its player (#944): a "See transcription" disclosure the reader
     opens, and the revealed block labelled a transcript so it never reads as the tenant's own typed
     text. A voice note with no transcript shows a plain note in its place. No confidence is shown
     (adr/0007). Colours are tokens, so both themes read. */
  .wa-tx{margin:6px 0 0}
  .wa-tx-sum{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;color:var(--ink-2);cursor:pointer;list-style:none;width:fit-content}
  .wa-tx-sum::-webkit-details-marker{display:none}
  .wa-tx-sum::before{content:"▸";color:var(--ink-3);font-size:10px}
  .wa-tx[open] .wa-tx-sum::before{content:"▾"}
  .wa-tx-sum:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:4px}
  .wa-tx-body{margin:6px 0 0;padding:8px 11px;border:1px solid var(--line);border-radius:9px;background:var(--canvas)}
  .wa-tx-label{font-size:10.5px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--ink-3);margin-bottom:3px}
  .wa-tx-text{margin:0;font-size:13px;color:var(--ink-2);white-space:pre-wrap}
  .wa-tx-note{margin:6px 0 0;font-size:12px;color:var(--ink-3);font-style:italic}

  /* AI held draft */
  .draft{align-self:stretch;max-width:none;border:1px solid var(--ai-line);border-radius:var(--r-lg);background:var(--ai-bg);overflow:hidden}
  .draft-h{display:flex;align-items:center;gap:8px;padding:10px 14px;border-bottom:1px solid var(--ai-line);color:var(--ai-ink)}
  .draft-h b{font-size:12.5px;font-weight:600;flex:1}
  .heldpill{font-size:10.5px;font-weight:600;color:var(--ai-ink);background:color-mix(in srgb,var(--ai-ink) 12%,transparent);border-radius:999px;padding:2px 8px}
  .draft-b{padding:12px 15px;font-size:13.5px;line-height:1.55;color:var(--ink)}
  .draft-f{display:flex;align-items:center;gap:8px;padding:0 14px 13px;flex-wrap:wrap}
  /* The held-draft actions use the shared AI-surface pair (.btn-primary / .btn-ghost) and tertiary
     (.btn-quiet) defined once in the design-system block above. No separate button family here. */

  /* internal note */
  .note{align-self:stretch;max-width:none;display:flex;gap:9px;padding:10px 13px;border-radius:var(--r-md);
    background:var(--sel);border:1px dashed var(--ink-faint)}
  .note .ico-sm{color:var(--ink-3);flex:none;margin-top:1px}
  .note .txt{font-size:12.5px;color:var(--ink-2);line-height:1.5}
  .note .txt b{color:var(--ink);font-weight:600}

  /* reply bar */
  .reply{flex:none;padding:12px 20px 16px;border-top:1px solid var(--line)}
  .reply-box{display:flex;flex-direction:column;align-items:stretch;gap:6px;padding:10px 12px 10px 14px;border:1px solid var(--line);border-radius:var(--r-lg);background:var(--canvas);transition:border-color .18s}
  .reply-box:focus-within{border-color:var(--ink-faint)}
  .reply-box .chn{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;font-weight:600;color:var(--ink-2);flex:none;margin-bottom:8px}
  .reply-box input,.reply-box textarea{flex:1;border:0;background:none;outline:none;font-size:13.5px;font-family:inherit}
  .reply-box textarea{resize:none;min-height:52px;max-height:160px;line-height:1.45;padding:8px 0}
  .reply-box input::placeholder,.reply-box textarea::placeholder{color:var(--ink-3)}
  .send{width:34px;height:34px;border-radius:9px;flex:none;display:grid;place-items:center;background:var(--ink);color:var(--canvas);transition:transform .12s var(--ease),filter .16s}
  .send:hover{filter:brightness(1.12)} .send:active{transform:scale(.92)}
  .send:disabled{opacity:.4;pointer-events:none}
  .reply-tools{display:flex;align-items:center;gap:8px;margin-top:8px;color:var(--ink-3);flex-wrap:wrap}
  .reply-tools .hint{margin-left:auto;font-size:11px;color:var(--ink-3)}
  #threadDraftNote,#cmpDraftNote{flex-basis:100%;margin:4px 0 0;color:var(--ink-3)}
  .open-slot{background:rgba(180,140,40,.18);padding:0 4px;border-radius:4px}
  .reply-brief{flex:1;min-width:10rem;border:1px solid var(--line);border-radius:8px;padding:6px 10px;font-size:12.5px;background:var(--canvas)}
  .addr-row{display:flex;flex-wrap:wrap;align-items:flex-start;gap:6px 8px;padding:4px 0;font-size:13px}
  .addr-row .k{color:var(--ink-3);width:36px;flex:none;font-size:12px;padding-top:6px;line-height:1.2}
  .addr-field{display:flex;flex-wrap:wrap;align-items:center;gap:4px 6px;flex:1;min-width:0;min-height:28px;cursor:text}
  .addr-chips{display:contents}
  .addr-chip{display:inline-flex;align-items:center;gap:2px;max-width:100%;padding:2px 6px 2px 9px;border:1px solid var(--line);border-radius:999px;background:color-mix(in srgb,var(--ink) 3.5%,var(--canvas));font-size:12.5px;line-height:1.35;color:var(--ink)}
  .addr-chip b{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .addr-chip button{width:16px;height:16px;border:0;border-radius:50%;background:none;color:var(--ink-3);cursor:pointer;padding:0;font-size:13px;line-height:1;display:grid;place-items:center}
  .addr-chip button:hover{background:var(--sel-2);color:var(--ink)}
  .addr-field input{flex:1;min-width:8rem;border:0;background:none;outline:none;font:inherit;padding:4px 0}
  .addr-cap{font-size:12px;color:var(--ink-3);margin:4px 0 0}
  .addr-err{color:var(--danger);font-weight:500}
  .addr-known{display:flex;flex-wrap:wrap;gap:6px;padding:4px 0 8px 44px}
  .addr-known button{font-size:11.5px;color:var(--ink-2);background:var(--bg);border:1px solid var(--line);border-radius:999px;padding:3px 8px}
  .addr-line-toggle{font-size:12px;font-weight:600;color:var(--ink-3);padding:2px 6px}
  .addr-line-toggle:hover{color:var(--ink)}
  .bcc-hint{flex:1 1 100%;margin:0 0 0 44px;font-size:11px;color:var(--ink-3)}
  .fmt{display:flex;align-items:center;gap:1px}
  .fmt button{width:30px;height:30px;border-radius:7px;color:var(--ink-2);display:grid;place-items:center}
  .fmt button:hover{background:var(--sel);color:var(--ink)}
  .fmt button.on{background:var(--sel-2);color:var(--ink)}
  .fmt-sep{width:1px;height:14px;margin:0 5px;background:var(--line);flex:none}
  .reply-box .chn{display:none}
  .reply-bar{display:flex;align-items:center;gap:8px;width:100%}
  .reply-bar .fmt{flex:1;min-width:0}
  .reply-box .editor{flex:1;min-height:72px;max-height:220px;overflow:auto;outline:none;font-size:13.5px;line-height:1.45;padding:4px 0 8px;font-family:inherit}
  .reply-box .editor p{margin:0 0 0.75em}
  .reply-box .editor p:last-child{margin:0}
  .reply-box .editor:empty:before{content:attr(data-placeholder);color:var(--ink-3);pointer-events:none}
  .reply-box .editor blockquote,.ml-cmp .ml-editor blockquote{margin:8px 0 0;padding:0 0 0 10px;border-left:2px solid var(--line);color:var(--ink-3);font-size:12.5px}
  .file-row{display:flex;flex-wrap:wrap;gap:6px;padding-top:6px}
  .file-chip{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border-radius:8px;background:var(--sel);font-size:12px}
  .file-chip button{border:0;background:none;color:var(--ink-3);cursor:pointer}
  .reply.sending .send{opacity:.4;pointer-events:none}
  /* The one Mentions rule with no canvas equal: when the mentioning comment is gone, the name slot
     reads as a quiet note instead of a person (#528). */
  .pp-mn-hd .mn-gone{font-weight:500;font-style:italic;color:var(--ink-3)}

  /* ---------- Context + History tab content ---------- */
  .ctxsec{padding:14px 0;border-bottom:1px solid var(--line-soft)}
  .ctxsec:first-child{padding-top:4px}
  .histlead{font-size:12.5px;color:var(--ink-3);padding:8px 0 10px;line-height:1.5}
  .histlead b{color:var(--ink-2);font-weight:600}
  .timeline{position:relative;padding-left:22px}
  .timeline::before{content:"";position:absolute;left:5px;top:5px;bottom:8px;width:1.5px;background:var(--line)}
  .tl-item{position:relative;padding:0 0 16px}
  .tl-item:last-child{padding-bottom:2px}
  .tl-item .tldot{position:absolute;left:-19px;top:3px;width:10px;height:10px;border-radius:50%;background:var(--canvas);border:2px solid var(--ink-faint)}
  .tl-head{font-size:11.5px;color:var(--ink-3);margin-bottom:3px}
  .tl-body{font-size:13px;color:var(--ink);line-height:1.45}
  .tl-body b{font-weight:600}
  .ctxsec:last-child{border-bottom:0}
  .ctxsec .lab{font-size:11px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-3);margin-bottom:8px}
  .prop{display:flex;gap:10px}
  .prop .thumb{width:44px;height:44px;border-radius:10px;flex:none;background:linear-gradient(140deg,var(--sel-2),var(--sel));display:grid;place-items:center;color:var(--ink-3)}
  .prop b{font-size:13.5px;font-weight:600;display:block}
  .prop span{font-size:12px;color:var(--ink-3)}
  .factrow{display:flex;justify-content:space-between;gap:10px;font-size:12.5px;padding:4px 0}
  .factrow .k{color:var(--ink-3)} .factrow .v{color:var(--ink);font-weight:500;text-align:right;min-width:0;word-break:break-word}
  /* The sender card redesign (#800). The card frame (.ml-crm, .ml-crm-h, .ml-dl, .ml-dlr, .ml-alto,
     .ml-issues) is the canvas sheet (`canvas/mail.css`), which loads before this module sheet; these
     module rules carry the quiet notes, the "Not available from <CRM>" group, the source footer and
     the "Previous matters" rows that sit with it (#374, #578, adr/0062). One neutral
     surface; no category fill (design standards §1). Every colour is a token, so both themes work. */
  /* The card and its "Previous matters" section, spaced as the canvas spaces the pane. */
  .ml-sender{display:flex;flex-direction:column;gap:28px}
  /* The not-verified disclosure is a quiet line, not a filled band (issue #800 item 3, adr/0006). */
  .crm-verify{font-size:12px;color:var(--ink-3);line-height:1.45;margin:0}
  .crm-sub{font-size:12.5px;color:var(--ink-3);line-height:1.5}
  .crm-note{font-size:12.5px;color:var(--ink-2);line-height:1.45;margin:0}
  /* The quiet notes inside the card body (not verified, staff check, stale read) align with the rows'
     horizontal padding and sit under the header, never on the card edge (issue #800 item 4). */
  .ml-crm > .crm-verify,.ml-crm > .crm-note{padding:8px 14px 0}
  .ml-crm > .crm-verify + .crm-note,.ml-crm > .crm-note + .crm-note{padding-top:4px}
  /* The per-role modules on the sender card (issue #932, adr/0070 decision 8). The card stacks one
     module for each CRM role the sender holds. Most roles are a dl of rows; the Tenant module below
     is a stack of grouped `.crm-grp` sections (#992). A module adds no surface and no fill of its own.
     A second module is set off from the one above by the same quiet divider the gap group and the
     footer use, so a reader sees where one role's records end and the next begin. */
  .crm-mod + .crm-mod{border-top:1px solid var(--line-soft)}
  /* The per-role module heading (#937). Each role module (Tenant, prospective Tenant, Landlord,
     Contractor and Supplier) leads with a heading naming its role, so a screen reader can navigate the
     card by role and announces the role when it reaches the module. The role already shows in the card
     header, so the heading is for assistive technology only and is hidden from sight — the same
     visually-hidden pattern the comments live region uses (off the flow, a 1px clipped box), which adds
     no visible content (issue #937 scope). */
  .crm-mod-h{
    position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
    clip:rect(0,0,0,0);white-space:nowrap;border:0
  }
  /* ===== The Tenant module, grouped layout (#992, the chosen "merged" board) =====
     The Tenant module stacks grouped sections — the Property line (owner 2026-10-13, #1100), the
     Tenancy fact grid, the checks with no date on record, and the dated key dates — instead of one
     long column of rows. The groups
     sit inside the card's horizontal padding, each set off from the one above by the same quiet
     divider the gap group and the footer use. No filled panel and no nested box; every colour is a
     token, so both themes work (design standards §1). The module is its own inline-size container, so
     the grid and the key-dates table reflow to the narrow context column without a horizontal scroll
     (#992 AC), independent of where the card sits. */
  .crm-tenant{container-type:inline-size}
  .crm-grp{padding:12px 14px}
  .crm-grp + .crm-grp{border-top:1px solid var(--line-soft)}
  /* The Tenancy coverage note (issue #917): the honesty line that says the list may be incomplete. It
     is the last line of the Tenant module, so it takes the same inset as the grouped sections above
     and the same quiet divider, not the card edge (review round 3). The Landlord module's detail note
     (a note about the work orders, not the Property list; #1396) is its last line too and takes the same. */
  .crm-tenant > .crm-note,.crm-landlord > .crm-note{padding:12px 14px;border-top:1px solid var(--line-soft)}
  .crm-grp > .monolab{display:block;margin:0 0 6px}
  .crm-grp-h{display:flex;align-items:center;gap:8px}
  .crm-grp-h > .monolab{margin:0 0 6px}
  /* The Tenancy fact grid: two columns at a wide width (dates on the left, service and money on the
     right), each a dl.ml-dl with no padding of its own. The 28px column gap keeps the two columns
     apart; the per-row 112px key column lines the values up. */
  .crm-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 28px}
  .crm-grid .ml-dl{padding:0}
  .crm-grid .ml-dlr{grid-template-columns:112px minmax(0,1fr);gap:10px;padding:7px 0}
  /* The Property line: the heading, the type and bedrooms, and the manager as a quiet note, on one
     wrapping line. */
  .crm-propline{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 18px}
  .crm-propline > .monolab{margin:0}
  .crm-prop-main{font-size:13.5px;color:var(--ink)}
  /* The Work orders on the Tenant's Property (#992 board, #999). One row per work order: the summary
     with the CRM reference number and the source priority word, a quiet status/supplier/reported
     sub-line, and the quote on the right. No verdict word and no status colour (adr/0008); the row
     carries no control, so the per-row CRM deep link parked for #870 is never a dead link. */
  .crm-wos{display:flex;flex-direction:column}
  .crm-wo-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
  .crm-wo{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:baseline;gap:4px 16px;padding:7px 0}
  .crm-wo + .crm-wo{border-top:1px solid var(--line-soft)}
  .crm-wo-main{min-width:0;display:flex;flex-direction:column;gap:2px}
  .crm-wo-quote{text-align:right;white-space:nowrap}
  /* The checks with no date on record. The count line reads the evidence; the names sit below it in
     the closed state. "Arrange with Helm" and the per-check "Arrange" are quiet AI-surface text
     buttons (the ink of the AI surface, not the brand green), and the disclosure chevron turns when
     the list opens. No verdict word and no status colour (adr/0008). */
  .crm-checks{display:flex;flex-direction:column;gap:6px}
  .crm-checks-top{display:flex;align-items:flex-start;gap:8px}
  .crm-checks-txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
  .crm-arrange{color:var(--ai-ink);padding:4px 6px}
  .crm-arrange:hover{background:var(--ai-bg)}
  .crm-checks-tog{padding:4px}
  .crm-checks-tog .ico{transition:transform .16s var(--ease)}
  .crm-checks-tog[aria-expanded="true"] .ico{transform:rotate(180deg)}
  .crm-checks-list{list-style:none;margin:2px 0 0;padding:0;display:flex;flex-direction:column}
  .crm-check{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:baseline;gap:12px;padding:7px 0;border-top:1px solid var(--line-soft);font-size:13.5px}
  .crm-check-label{min-width:0;word-break:break-word}
  .crm-checks-offer{color:var(--ink-3)}
  /* The dated key dates: the CRM's own label, "Coming up" or "Earlier", and the effective date. Three
     columns at a wide width; the status and the date sit on fixed 96px tracks so both line up from row
     to row whatever the date's own width (the chosen board, #1037). The narrow query puts the date
     beside the label and the word below it. */
  .crm-keydates{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
  .crm-kd{display:grid;grid-template-columns:minmax(0,1fr) 96px 96px;gap:4px 14px;align-items:baseline;padding:7px 0;font-size:13.5px}
  .crm-kd + .crm-kd{border-top:1px solid var(--line-soft)}
  .crm-kd-label{min-width:0;word-break:break-word}
  .crm-kd-when{text-align:right}
  /* The dates line up in a column, so they use tabular figures (#1472). */
  .crm-kd-when{font-variant-numeric:tabular-nums}
  /* ===== The prospective Tenant module (the chosen board, owner 2026-10-12) =====
     It reuses the Tenant module's grouped parts — the two-column fact grid (.crm-grid), the key dates
     table (.crm-keydates) and the quiet one-line group — so it is its own inline-size container too,
     and the grid and the tables reflow to the narrow context column without a horizontal scroll. No
     filled panel and no nested box; every colour is a token, so both themes work (design standards §1). */
  .crm-applicant{container-type:inline-size}
  /* The Viewings list: the key dates table pattern with wider middle and right columns for the CRM's
     own status and the date and time (owner 2026-10-12). The status reads in words (.crm-kd-status),
     so a cancelled viewing never relies on colour alone (design standards §4). */
  .crm-viewings .crm-kd{grid-template-columns:minmax(0,1fr) minmax(0,176px) minmax(0,150px)}
  .crm-kd-status{min-width:0;word-break:break-word}
  /* The viewings coverage note (#1418): its own quiet line under the list, inside the Viewings group. */
  .crm-viewings + .crm-viewings-note{margin-top:6px}
  /* The Enquiries group: the heading and the enquiry Properties on one quiet wrapping line. */
  .crm-enquiries{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 18px}
  .crm-enquiries > .monolab{margin:0}
  .crm-enq-line{font-size:13.5px;color:var(--ink)}
  /* ===== The Contractor and Supplier module (the chosen board, owner 2026-10-12; #936) =====
     It reuses the two-column fact grid (.crm-grid) and the work-order rows (.crm-wos) the other role
     modules use, so it is its own inline-size container too and the grid reflows to the narrow context
     column without a horizontal scroll. No filled panel and no nested box; every colour is a token, so
     both themes work (design standards §1). The open-work-order count is the one emphasised value. */
  .crm-contractor{container-type:inline-size}
  .crm-strong{font-weight:600}
  /* ===== The Landlord module (#935) =====
     The chosen tabbed board: the app's segmented control (.seg, from the shell) under the module
     header, then a Summary tab and one tab for each let Property. It reuses the Tenant card's grouped
     parts — the two-column fact grid (.crm-grid), the work orders, the checks and the key dates table —
     so a Property tab matches the Tenant module one to one, and it is its own inline-size container so
     those parts reflow to the narrow context column without a horizontal scroll. No filled panel and no
     nested box; every colour is a token, so both themes work (design standards §1). */
  .crm-landlord{container-type:inline-size}
  .crm-tabs{padding:10px 14px 2px}
  .crm-tabs .seg{max-width:100%;flex-wrap:wrap}
  .crm-tabpanel[hidden]{display:none}
  /* The Summary Properties list: one row for each let Property, a button that opens that Property's tab.
     The address on the left, a quiet status and agreed rent in the middle, a chevron on the right. */
  .crm-prop-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
  .crm-prop + .crm-prop{border-top:1px solid var(--line-soft)}
  .crm-prop-btn{display:grid;grid-template-columns:minmax(0,1fr) auto 14px;gap:12px;align-items:baseline;width:100%;padding:7px 0;color:var(--ink);text-align:left}
  .crm-prop-addr{min-width:0;word-break:break-word;font-size:13.5px}
  .crm-prop-meta{text-align:right;white-space:nowrap}
  .crm-prop-chev{color:var(--ink-3);align-self:center}
  .crm-prop-more{margin:2px 0 0}
  /* The "This thread" tag (#1052): the one let Property the conversation is about. A quiet brand-tinted
     chip — a label, never a verdict, with no confidence score (adr/0007). On a Property tab it leads the
     panel (a .crm-thread line above the Tenancy grid); on the Summary row it sits inline in the address
     cell, so the row keeps its grid. Every colour is a token, so both themes read correctly. */
  .crm-thread{margin:0;padding:12px 14px 0}
  .crm-thread-tag{display:inline-flex;align-items:center;font-size:10.5px;font-weight:600;letter-spacing:.02em;color:var(--brand);background:color-mix(in srgb,var(--brand) 12%,transparent);border-radius:999px;padding:2px 8px}
  .crm-prop-addr .crm-thread-tag{margin-left:8px;vertical-align:middle}
  @container (max-width:520px){
    .crm-grid{grid-template-columns:minmax(0,1fr)}
    /* The Summary Property row at narrow width: the status and agreed rent drop below the address, so
       the nowrap meta column never forces the narrow context column to scroll sideways (design
       standards §5; #935 round 1 review). */
    .crm-prop-btn{grid-template-columns:minmax(0,1fr) 14px}
    .crm-prop-addr{grid-column:1;grid-row:1}
    .crm-prop-chev{grid-column:2;grid-row:1}
    .crm-prop-meta{grid-column:1;grid-row:2;text-align:left;white-space:normal}
    .crm-checks-top{flex-wrap:wrap}
    .crm-arrange{order:3;flex-basis:100%}
    .crm-kd{grid-template-columns:minmax(0,1fr) auto}
    .crm-kd-label{grid-column:1;grid-row:1}
    .crm-kd-when{grid-column:2;grid-row:1}
    .crm-kd-bucket{grid-column:1;grid-row:2}
    /* The Viewings table at narrow width: the Property on the first line, then the status and the date
       and time on the second line, so nothing overflows sideways (design standards §5). */
    .crm-viewings .crm-kd{grid-template-columns:minmax(0,1fr) auto}
    .crm-kd-status{grid-column:1;grid-row:2}
    .crm-viewings .crm-kd-when{grid-column:2;grid-row:2}
  }
  /* The quiet "Not available from <CRM>" group at the end of the card (issue #800, owner 2026-10-09). */
  .crm-gaps{padding:10px 14px;border-top:1px solid var(--line-soft);display:flex;flex-direction:column;gap:3px}
  .crm-gaps-h{font-size:11.5px;font-weight:600;color:var(--ink-3);line-height:1.4;margin:0}
  .crm-gaps .crm-note{color:var(--ink-3)}
  /* The source footer: where the data came from and when, as a quiet line (issue #800 item 5). */
  .crm-foot{padding:9px 14px;border-top:1px solid var(--line-soft);display:flex;flex-direction:column;gap:2px}
  .crm-src{font-size:11px;color:var(--ink-3)}
  /* The read line and the "Refresh from <CRM>" control on the Sender tab (#1308): the read time at
     the left, the refresh button at the right, wrapping on a narrow pane. */
  .crm-refresh{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;padding:8px 14px}
  .crm-refresh .crm-src{flex:1 1 auto}
  /* The "Previous matters" section below the card. Each row is the date and the one-line
     summary; the canvas `.ml-issues li + li` rule carries the divider between rows. */
  .ml-issues-sec{display:flex;flex-direction:column;gap:8px}
  .ml-issues-sec > .t-sec{margin:0}
  .ml-issue{display:grid;grid-template-columns:92px minmax(0,1fr);gap:12px;align-items:baseline;padding:9px 4px;font-size:13.5px}
  .ml-issue .w{font-size:12.5px;color:var(--ink-3)}
  /* The dates line up in the fixed date column, so they use tabular figures (#1472). */
  .ml-issue .w{font-variant-numeric:tabular-nums}
  .ml-issue-t{color:var(--ink);min-width:0;word-break:break-word}
  @container mlstage (max-width: 900px){
    .ml-issue{grid-template-columns:minmax(0,1fr)}
    .ml-issue .w{grid-row:2}
  }
  /* Small text action for the older history. It uses an ink token, not the brand green, so it meets
     WCAG 2.2 AA on the light canvas and matches the other small text toggles (#375, design
     standards §2, §4). */
  .crm-more{font-size:12px;font-weight:600;color:var(--ink-2);padding:7px 0;text-align:left}
  .crm-more:hover{color:var(--ink)}
  /* The revealed older-issues list carries the `ml-issues` class too, and the canvas sets
     `.ml-issues{display:flex}`, which overrides the browser's `[hidden]` default. Without this guard
     the list stays on screen while `hidden` is set, so the "Show more" disclosure shows nothing new
     and aria-expanded reads false over visible rows (issue #937). The `[hidden]` attribute raises the
     specificity above `.ml-issues`, so the list is truly hidden until the operator opens it — the same
     guard `.crm-tabpanel[hidden]` uses for the Landlord tab panels. */
  .crm-more-list[hidden]{display:none}
  /* The revealed older history takes focus for place-keeping when the control hides itself. A keyboard
     operator must see where focus went, so it keeps the visible focus ring (WCAG 2.2 2.4.7, design
     standards §4). The ring shows for keyboard focus only, so a pointer reveal stays quiet. */
  .crm-more-list:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:6px}
  .person{display:flex;align-items:center;gap:9px;padding:6px 0}
  .person .av{width:30px;height:30px;font-size:11px}
  .person .who{min-width:0;flex:1}
  .person .who b{font-size:12.5px;font-weight:600;display:block}
  .person .who span{font-size:11px;color:var(--ink-3);display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .person .rolechan{color:var(--ink-faint)}
  .pastitem{display:flex;gap:9px;padding:7px 0;font-size:12.5px}
  .pastitem .ico-sm{color:var(--ink-3);flex:none;margin-top:1px}
  .pastitem .p b{font-weight:600} .pastitem .p span{color:var(--ink-3);display:block;font-size:11.5px}
  .srcs{display:flex;flex-wrap:wrap;gap:6px}
  .srcs .s{font-size:11px;color:var(--ink-2);background:var(--bg);border-radius:7px;padding:4px 8px;display:inline-flex;align-items:center;gap:5px}
  .srcs .s .ico-xs{color:var(--ink-3)}

  /* ============================== Compose overlay ============================== */
  /* animation:none holds the canvas base `.scrim` one-shot fade off this persistent shell scrim,
     which shows through an opacity transition instead; without it the overlay would flash on load
     (#568, the shell frame reconciles this in #574). */
  /* animation:none holds the canvas base `.scrim` one-shot fade off this persistent shell scrim
     (the cascade guard in designSystem.test.ts asserts it stays fixed and unanimated). */
  .scrim{position:fixed;inset:0;background:rgba(30,26,20,.34);opacity:0;pointer-events:none;transition:opacity .28s;z-index:40;animation:none}
  .scrim.on{opacity:1;pointer-events:auto}

  /* The compose dialog is the canvas centred `.dlg.wide.ml-cmp` over the `.dlg-scrim` (helm.css,
     ported for #579). It is hidden by the `hidden` attribute; the `.dlg` author rule sets
     display:flex, so the hide is made explicit here (as the To-do and Calendar dialogs do). The
     editor keeps its empty-state placeholder, and the Send button dims while a send is in flight. */
  .ml-cmp[hidden]{display:none}
  /* The canvas `.dlg-scrim` and `.dlg` are position:absolute. These two children of #inbox-main
     have no positioned ancestor today, so they centre on the viewport; pin them to the viewport so
     a later shell wrap that sets position:relative on #inbox-main cannot move them (#579). A scoped
     rule here, not an edit to the shared canvas helm.css. */
  #cmpScrim{position:fixed}
  .ml-cmp{position:fixed}
  /* Compose header and channel-row insets, kept off inline styles so they stay in step with the
     canvas sheet and with light/dark restyles (#579). */
  .ml-cmp .cmp-from{margin-top:6px}
  .ml-cmp .cmp-chrow{padding:4px 0 10px}
  .ml-cmp .ml-editor:empty:before{content:attr(data-placeholder);color:var(--ink-3);pointer-events:none}
  /* ===== Start a new WhatsApp message from compose (#945, adr/0069 decisions 6 and 7) =====
     On the WhatsApp channel the composer shows a contact chooser, a closed-window banner, a status
     line and one action that opens the chosen contact's thread to write or to choose a template. The
     chooser reuses the shared form-input control (.inp2); only this panel's layout is set here. */
  .ml-cmp #cmpWaPanel{display:flex;flex-direction:column;gap:var(--sp-3)}
  .ml-cmp #cmpWaPanel .cmp-wa-status{margin:0;font-size:13px;line-height:1.4;color:var(--ink-3)}
  .ml-cmp #cmpWaPanel .cmp-wa-go{align-self:flex-start}
  /* The `.ml-cmp.sending .btn-ink` sending-state rule stays in the shell (see the inline <style>),
     so the design-system button check reads the shared `.btn-ink` definition first. */
  /* The toast rules, and the sign-in offset that rides with them, stay in the shell (see the inline
     <style>), because showToast lives in the shell and every module raises toasts through it. */

  /* ====================== Mail module layout (#260, adr/0063) ======================
     Composes the shared design system only: the buttons, fields, headings, .ds-menu, .chip, .ban,
     .empty and .toast come from the fenced design-system block above. These rules place the Mail
     module's own pieces — the reader header menus, the channel filter and the reader avatar badge —
     and hold the reader up when the stage narrows. No new button, field or heading style here. */
  .rd-id .av{position:relative}
  /* The reader-actions row base (.rd-actions) comes from the canvas sheet; this position:relative
     override is reader chrome, so it lives with the Mail module. It only sets position, which the
     canvas .rd-actions rule does not, so ship order does not change what wins. */
  .rd-actions{position:relative}
  .rd-av-ini{line-height:1}
  .rd-more{position:relative;display:inline-flex}
  .rd-moremenu{position:absolute;right:0;top:calc(100% + 6px);min-width:200px;z-index:30}
  .chan-dd{position:relative;display:inline-flex;align-items:center}
  .chan-chip .cx{color:var(--ink-faint);margin-left:2px}
  .chan-menu{position:absolute;right:0;top:calc(100% + 8px);min-width:220px;z-index:30}

  /* ===== The thread reader inside the Mail stage (#578) =====
     The reader (.ml-rdr) and its scrim (.rdr-scrim) are positioned inside #listPane.ml-stage, so the
     scrim dims only the list. They stay in the DOM and show with the `open`/`on` classes the reader
     code already toggles, so the Escape handler and the focus code are unchanged. The three tab
     panels live in the DOM together; the reader's data-tab shows the active one. mail.css carries the
     look (.ml-rdr, .ml-head, .ml-subchips, .ml-thread, .ml-reply, .ml-ctxpane, .ml-tl, .ml-cd) and
     the narrow-stage container queries; these rules only wire the show/hide and the panel switch. */
  #reader{display:none}
  #reader.open{display:grid}
  #readerScrim{display:none}
  #readerScrim.on{display:block}
  .ml-conv{display:none;flex-direction:column;min-height:0}
  #reader[data-tab="conversation"] .ml-conv{display:flex;flex:1;min-height:0}
  .ml-ctxpane[data-panel],.ml-tl[data-panel]{display:none}
  #reader[data-tab="context"] .ml-ctxpane{display:flex;flex:1;min-height:0}
  #reader[data-tab="history"] .ml-tl{display:block;flex:1;min-height:0}
  /* The History times line up in the right-hand column (canvas `.ml-tl li`), so they use tabular
     figures (#1472). */
  .ml-tl .w{font-variant-numeric:tabular-nums}
  /* The conversation panel stacks the hold note, the scrolling thread and the pinned reply. */
  .ml-conv .ml-thread{flex:1;min-height:0}
  /* The comments drawer body holds the scrolling comment list; the list itself does not scroll, so
     there is one scroll region (the canvas .ml-cd-b). */
  .ml-cd-b .comment-list{flex:none;overflow:visible;padding:0}

  /* ===== The Mail list stage (#570) =====
     The Mail pane is the canvas module stage: the 22px canvas gutter on the head, chips, day labels
     and rows (the rows' own grid comes from mail.css .ml-r/.ml-open/.ml-side/.ml-star), the deeper
     scroller tail, the filter chips with the channel chip pushed to the end, and the centred empty
     state. Scoped to #listPane.is-mail, so the other modules that still share the pane keep their
     own chrome until each module ports. */
  #listPane.is-mail .list-head{padding:18px 22px 10px}
  #listPane.is-mail .chips{padding:10px 22px 12px}
  #listPane.is-mail .daylab{padding:14px 22px 6px}
  #listPane.is-mail .scroller{padding-bottom:90px;position:relative}
  #listPane.is-mail .chips .chan-dd{margin-left:auto}
  #listPane.is-mail .ml-r:hover .badge{border-color:var(--sel)}
  #listPane.is-mail .ml-r.sel .badge{border-color:var(--sel-2)}
  /* The empty state is centred in the stage (canvas empty v2.1), beating the shared in-flow hold
     that keeps the un-ported module panes' empties at the top of the list. */
  #listPane.is-mail .empty{position:absolute;inset:0;margin:auto;height:max-content;max-width:min(400px,calc(100% - 44px));padding:24px}
  /* Save draft sits in the reply bar, beside Send (adr/0063 owner decision 2026-10-05, #260). The
     formatting controls take the free space, so Save draft and Send sit together on the right. */
  .reply-bar #save{flex:none}

  /* ===== The WhatsApp reply area and the closed-window notice (#941) =====
     A WhatsApp thread is one shared agency chat (adr/0069): its reply bar drops the email-only
     controls (hidden by the hidden attribute in mail.js), and when the 24-hour window is closed it
     shows the closed-window notice and makes the editor read-only. The notice reuses the shared warn
     banner (.ban.warn); inside the reply bar it sits flush, so its side margin is cleared. */
  .ml-reply.is-whatsapp .wa-window{margin:0 0 var(--sp-3)}
  .ml-reply.is-whatsapp #replyInput[contenteditable="false"]{background:var(--raised);color:var(--ink-3);cursor:not-allowed}
  /* ===== The template picker outside the 24-hour window (#766, adr/0069 decision 7) =====
     When the window is closed, the reply area offers only the approved templates: a template chooser,
     one input per variable the staff member fills, and a Send. A template that names a property or a
     tenancy is withheld from an unmatched thread, matching the server's disclosure gate. */
  /* The select and the variable inputs reuse the shared form-input control (.inp2) so they match the
     fields elsewhere in the app; only the picker's own layout is set here. */
  .ml-reply.is-whatsapp .wa-template-picker{display:flex;flex-direction:column;gap:var(--sp-2);margin:0 0 var(--sp-3)}
  .ml-reply.is-whatsapp .wa-tpl-label{font-size:12px;font-weight:600;color:var(--ink-2)}
  .ml-reply.is-whatsapp .wa-tpl-vars{display:flex;flex-direction:column;gap:var(--sp-2)}
  .ml-reply.is-whatsapp .wa-tpl-var{display:flex;flex-direction:column;gap:3px}
  .ml-reply.is-whatsapp .wa-tpl-var-label{font-size:11.5px;color:var(--ink-3)}
  .ml-reply.is-whatsapp .wa-tpl-status{font-size:12px;line-height:1.4;color:var(--ink-3)}
  /* The email copy block for a template that points to email (#1068): the subject and body of the
     email the contact gets on the channel of record, reviewed in the same step as the WhatsApp
     message. It reads as one group, set off from the variable inputs above. */
  .ml-reply.is-whatsapp .wa-tpl-email{display:flex;flex-direction:column;gap:3px;padding-top:var(--sp-2);border-top:1px solid var(--line)}
  .ml-reply.is-whatsapp .wa-tpl-email-note{font-size:11.5px;line-height:1.4;color:var(--ink-3);margin-bottom:3px}
  .ml-reply.is-whatsapp .wa-tpl-email-body{min-height:76px;resize:vertical}
  /* The note that says why a template that points to email is not offered (no email on file, #1068). */
  .ml-reply.is-whatsapp .wa-tpl-withheld{font-size:12px;line-height:1.4;color:var(--ink-3)}
  .ml-reply.is-whatsapp .wa-tpl-send{align-self:flex-start}
  /* The template Send shows a pending state while a send is in flight, the same `sending` state the
     reply pane carries, so a staff member sees the send is working and cannot press it twice (#766
     review, design standards §3). */
  .ml-reply.is-whatsapp.sending .wa-tpl-send{opacity:.4;pointer-events:none}
  /* ===== Who is replying on a shared WhatsApp thread (#942, adr/0069 decision 4) =====
     While a colleague has the draft open, the reply bar shows a quiet "<name> is replying" line, so
     two staff do not write the same reply. It is a notice, not a lock: it never disables the editor
     or the send. A small presence dot in the WhatsApp channel accent leads the line. */
  .ml-reply.is-whatsapp .wa-presence{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-2);margin:0 0 var(--sp-2);
    font-size:12px;line-height:1.4;color:var(--ink-3)}
  /* A long display name wraps onto the next line instead of overflowing the reply pane at phone
     width; the name text may also break within itself on a very narrow pane. */
  .ml-reply.is-whatsapp .wa-presence > span:last-child{min-width:0;overflow-wrap:anywhere}
  .ml-reply.is-whatsapp .wa-presence-i{width:7px;height:7px;border-radius:50%;background:var(--wa);flex:none}
  /* ===== Shared WhatsApp thread assignment (#763, adr/0069 decision 4) =====
     The assignee shows on the thread row and in the reader header, so every staff member at the
     agency sees who owns a shared WhatsApp thread. The row chip is the owner's initials in a small
     pill; the "me" variant uses the WhatsApp channel accent so a staff member spots their own
     threads. The header chip reuses the sub-chip shape (.lchip) and leads with an initials disc. */
  .ml-asg{font-size:10px;font-weight:600;line-height:16px;color:var(--ink-3);background:var(--sel);
    border-radius:999px;padding:0 6px;flex:none;letter-spacing:.02em}
  .ml-asg.me{color:var(--on-brand);background:var(--wa)}
  .ml-asg-chip .ml-asg-i{display:inline-grid;place-items:center;width:16px;height:16px;border-radius:50%;
    font-size:9px;font-weight:600;color:var(--on-brand);background:var(--wa);margin-right:4px}
  .ml-asg-menu{min-width:180px}
  /* ===== The sender card person selector (#1412) =====
     A row of name chips above the sender card when a thread has more than one person. It reuses the
     canvas chip (.chip, .chip.on from the shell) and adds a small initials disc in the avatar tokens.
     The row wraps, and a long name or address is cut with an ellipsis inside its chip, so the narrow
     context column never scrolls sideways. The two-class selectors beat the shell's one-class .chip
     padding, which loads after this sheet. Every colour is a token, so both themes work. */
  .ml-people{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 12px;min-width:0}
  .ml-people .chip{max-width:100%;min-width:0;padding:3px 10px 3px 3px}
  .ml-people-av{display:inline-grid;place-items:center;width:20px;height:20px;border-radius:50%;flex:none;
    font-size:9.5px;font-weight:600;letter-spacing:-.02em;background:var(--av-bg);color:var(--av-ink)}
  .ml-people-nm{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
