/* Styles for the loan officer assistant.
   Split out of index.html on 2026-09-17: the single file had reached ~1,370
   lines of markup, styling and logic together, which made every change
   slower and riskier than it needed to be. No build step — the page loads
   this directly. */

/* ===================== Design tokens =====================
   Leadership Mortgage's own colours (owner, 2026-09-23): dark green ground,
   white type, mint for icons and frames. The two brand colours are exact; the
   surfaces are lifted from the green so panels read as the same material
   rather than grey boxes dropped on top.

   Gold is the contrast accent. The owner wanted "a darker yellow" and had no
   hex, so #D8A43C was chosen for legibility on the dark green — it clears the
   4.5:1 contrast bar for normal text, which a brighter yellow would not. Easy
   to swap: every use of it comes through --accent. */
  :root{

    /* ===================== Type scale =====================
       Six steps, named for the job. Every piece of text takes one of these
       and never a bare number: three elements in one week rendered at the
       browser's default 16px because nothing sized them at all — the TO and
       SUBJECT fields, the body of an open note, and a history date whose
       rule was deleted along with its parent. A bare number cannot be
       audited, and a missing one is invisible until it looks wrong.

       The sizes are the ones already in use. 13, 15, 18 and 20 folded into
       their nearest neighbour, which is the consolidation rather than a
       side effect of it — those four were the discrepancies.

       Raised across the board on 2026-10-13 at the owner's request. The old
       scale was drawn for a dark ground where thin light text looks heavier
       than it measures; on white, 12px body was simply small. Every step went
       up together, so the relationships between them — and every screen —
       hold.

       Several rules used to say `--text-heading` was 16px "to stop iOS zooming
       the page on input focus". That was wrong twice over: the value was 14px
       for weeks, and this app's viewport declares `maximum-scale=1,
       user-scalable=no`, so that zoom cannot happen here at all. The claim is
       gone; inputs take whatever step reads right beside their label. */
    --text-display: 34px;
    --text-title: 20px;
    --text-heading: 17px;
    --text-body: 15px;
    --text-small: 13px;
    --text-label: 11px;
    --brand-green:   #2E4437;   /* the brand's dark green */
    --brand-mint:    #95D1AD;   /* the brand's mint */

    /* White ground, green buttons (owner, 2026-10-13). The app ran dark green
       until then; every screen's colour comes from the handful of values
       below, which is why turning it over was six lines and a short list of
       fixes rather than a rewrite.

       Light changes what the brand colours can do. Mint on white is barely
       there, so anything that *reads* as the brand — text, icons, the ring
       round a live mic — takes `--accent-brand`, a green dark enough to be
       read. Mint survives where it is a fill with dark text on it, which is
       where it looks best anyway. */
    --bg:            #FFFFFF;   /* app background */
    --surface:       #F5F8F5;   /* cards / panels — white, warmed toward the green */
    --surface-alt:   #EBF1EC;   /* nested panels */
    /* The one tinted surface, for the block on a screen that is about what
       has NOT happened yet — the Tasks card on a deal (owner, 2026-11-04:
       *"perhaps highlighted in a different color to stand out"*). Mint at
       about a fifth over white, which is as far as it can go and still carry
       every foreground this app uses: measured against #EAF6EF, --text is
       14.6:1, --accent-brand 5.7:1, --accent 4.7:1 and --text-dim 4.9:1, so
       all four clear 4.5:1. Full-strength --brand-mint would not —
       --accent-brand on it is 3.6:1 — which is why this is a tint and not
       the brand colour. */
    --surface-mint:  #EAF6EF;
    --border:        #D3DFD6;
    --text:          #17241C;   /* near-black, tinted to the brand */
    --text-dim:      #5C6E62;
    /* Every one of these is checked against the ground it sits on: at least
       4.5:1 as text on white, and again as white text on the same colour
       filled. A pale gold that looked right on dark green was unreadable. */
    --accent:        #96610A;   /* gold: attention, actions, highlights */
    --accent-brand:  #2F6B48;   /* the brand green, as a foreground accent */
    --accent-live:   #C0442A;   /* recording — warm, not alarm red */
    --accent-think:  var(--accent-brand);  /* processing */
    --accent-done:   var(--accent-brand);  /* done */
    /* Text that sits ON a filled accent. White, now the fills are dark. */
    --on-accent:     #FFFFFF;
    --font: Poppins, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  }

  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

  html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font);
    overscroll-behavior-y: none;
  }

  body {
    /* Respect iOS safe areas (notch / home indicator) so it feels native */
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
    display: flex;
    flex-direction: column;
    /* 100vh on iPhone Safari counts the area behind the browser's own
       toolbar, so the bottom of the page — the tab bar — sits off-screen.
       100dvh is the visible height and updates as the toolbar hides; the vh
       line stays as the fallback for anything that doesn't support it. */
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
  }

  /* A browser without `dvh` falls back to the `100vh` line above, and on
     Android that counts the strip behind the browser's own toolbar: the shell
     ends up taller than the screen, and because it does not scroll, the
     bottom simply goes missing — taking the home screen's keys with it.
     Where dvh is missing, app.js measures the visible viewport and sets
     --app-h instead. A guard for those browsers only: the bottom row the
     owner lost on 2026-10-28 was Chrome, which has dvh. */
  body.measured-height { height: var(--app-h); }

  /* ===================== Header ===================== */
  header {
    flex: 0 0 auto;
    padding: 18px 20px 8px;
    text-align: center;
  }

  header h1 {
    margin: 0;
    font-size: var(--text-heading);
    font-weight: 600;
    letter-spacing: 0.2px;
  }

  header p {
    margin: 4px 0 0;
    font-size: var(--text-small);
    color: var(--text-dim);
  }

  /* ===================== Main layout ===================== */
  main {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    padding: 24px 20px;
    gap: 20px;
    min-height: 0; /* allow children to shrink/scroll */
  }

  /* ===================== Mic button + pulse rings ===================== */
  .mic-zone {
    flex: 0 0 auto;
    position: relative;
    /* Halved (owner, 2026-09-29). It was 220px of a phone screen spent on a
       button you press twice, while the words you actually said were in a box
       below it. */
    width: 132px;
    height: 132px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-top: 8px;
  }

  .ring {
    position: absolute;
    border-radius: 50%;
    border: 2px solid var(--accent);
    opacity: 0;
    pointer-events: none;
  }

  /* Rings only animate while listening (class toggled by JS) */
  .mic-zone.listening .ring {
    animation: pulse 1.8s ease-out infinite;
    border-color: var(--accent-live);
  }
  .mic-zone.listening .ring:nth-child(1) { animation-delay: 0s; }
  .mic-zone.listening .ring:nth-child(2) { animation-delay: 0.6s; }
  .mic-zone.listening .ring:nth-child(3) { animation-delay: 1.2s; }

  @keyframes pulse {
    0%   { width: 84px; height: 84px; opacity: 0.55; }
    100% { width: 132px; height: 132px; opacity: 0; }
  }

  /* Green and filled, like the six keys on the home screen. On white the old
     near-white circle with a hairline border read as a placeholder rather
     than as the main control on the screen. */
  #micBtn {
    position: relative;
    z-index: 2;
    width: 84px;
    height: 84px;
    border-radius: 50%;
    border: none;
    background: var(--brand-green);
    box-shadow: 0 0 0 1px var(--border), 0 6px 16px rgba(23,36,28,0.10);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
  }

  #micBtn:active {
    transform: scale(0.96);
  }

  #micBtn svg {
    width: 34px;
    height: 34px;
    fill: var(--brand-mint);
    transition: fill 0.2s ease;
  }

  /* Listening state: red glow */
  .mic-zone.listening #micBtn {
    background: var(--accent-live);
    box-shadow: 0 0 0 1px var(--accent-live), 0 0 32px rgba(192,68,42,0.25);
  }
  .mic-zone.listening #micBtn svg { fill: #FFFFFF; }

  /* Unsupported state: dimmed, non-interactive look */
  .mic-zone.unsupported #micBtn {
    opacity: 0.4;
  }

  /* ===================== Stop control (secondary, explicit) ===================== */
  .mic-status {
    flex: 0 0 auto;
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 8px;
    min-height: 18px;
    padding: 0 16px;
    text-align: center;
  }
  #statusText {
    font-size: var(--text-small);
    color: var(--text-dim);
    overflow-wrap: break-word;
  }

  /* ===================== Transcript ===================== */
  .panel {
    width: 100%;
    max-width: 480px;
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 18px;
    padding: 14px 16px;
  }

  .panel-label {
    font-size: var(--text-label);
    font-weight: 600;
    color: var(--text-dim);
    letter-spacing: 0.4px;
    margin-bottom: 8px;
  }

  #transcript {
    flex: 1 1 auto;
    overflow-y: auto;
    font-size: var(--text-body);
    line-height: 1.45;
    color: var(--text);
    word-wrap: break-word;
  }

  #transcript .interim {
    color: var(--text-dim);
  }

  #transcript:empty::before {
    content: "Your words will appear here as you speak.";
    color: var(--text-dim);
  }

  /* ===================== Status ===================== */
  .status-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--text-dim);
    flex: 0 0 auto;
    transition: background 0.2s ease;
  }

  .status-dot.live    { background: var(--accent-live); }
  .status-dot.think   { background: var(--accent-think); }
  .status-dot.done    { background: var(--accent-done); }

  /* ===================== Text entry ===================== */
  /* Same command path as dictation, clean input. Per ARCHITECTURE.md's
     build-order decision, actions get exercised on text before voice is
     trusted with them — and this is the only usable path at all on browsers
     where the Web Speech API is missing (notably iOS Safari). */
  .text-entry {
    width: 100%;
    max-width: 480px;
    flex: 0 0 auto;
    display: flex;
    gap: 8px;
  }

  #textInput {
    flex: 1 1 auto;
    min-width: 0;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 12px 14px;
    color: var(--text);
    font-family: var(--font);
    font-size: var(--text-heading);
  }
  #textInput::placeholder { color: var(--text-dim); }
  #textInput:focus { outline: none; border-color: var(--accent-think); }

  #textSendBtn {
    flex: 0 0 auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 0 18px;
    color: var(--text);
    font-family: var(--font);
    font-size: var(--text-body);
    font-weight: 600;
  }
  #textSendBtn:active { transform: scale(0.97); }

  /* ===================== Note confirmation card ===================== */
  /* Shown after a note is saved. The AI's call on who was actually spoken
     with is a suggestion; this is where a wrong call is visible and one tap
     to fix (see "Note capture" in ARCHITECTURE.md). */
  #noteCard {
    width: 100%;
    max-width: 480px;
    /* Takes the transcript panel's place while open (see .note-open below):
       on a phone there isn't room for the mic, the transcript and this. */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 12px 16px;
    font-size: var(--text-body);
    line-height: 1.4;
  }
  #noteCard[hidden] { display: none; }
  /* Scoped to the Talk screen on purpose: while a result card is open the
     transcript makes way for it, but this once hid EVERY panel in the app,
     so switching to To do or Contacts with a card open showed a blank screen
     (owner, 2026-09-23 — "the tab shows a 5 but nothing shows up"). */
  body.note-open #talkView .panel { display: none; }
  #noteCard .note-heard { color: var(--text-dim); font-style: italic; margin: 0 0 8px; }
  #noteCard .note-meta { color: var(--text-dim); font-size: var(--text-small); margin-bottom: 6px; }
  #noteCard .note-summary { margin: 0 0 8px; }
  #noteCard .recap-heading {
    font-size: var(--text-small); font-weight: 600; color: var(--accent);
    margin: 10px 0 2px; letter-spacing: 0.2px;
  }
  /* Dictation arrives as paragraphs and "- " lines; keep both as written. */
  #noteCard .recap-body { margin: 0; white-space: pre-wrap; }
  #noteCard .note-commitment { margin: 0 0 8px; }
  #noteCard .note-commitment strong { color: var(--accent); font-weight: 600; }
  #noteCard .note-people-label {
    font-size: var(--text-label); font-weight: 600; color: var(--text-dim);
    letter-spacing: 0.4px; margin: 4px 0 6px;
  }
  .note-person { padding: 8px 0; border-top: 1px solid var(--border); }
  .note-people-label + .note-person { border-top: none; }
  .note-person label { display: flex; align-items: center; gap: 10px; }
  .note-person input[type="checkbox"] {
    width: 20px; height: 20px; flex: 0 0 auto; accent-color: var(--accent-done);
  }
  .note-person .how { color: var(--text-dim); font-size: var(--text-small); }
  /* The name the app is asking about. No checkbox beside it: there is nothing
     to tick until it is settled. */
  .note-person .person-name { font-weight: 600; }
  /* The 30px indent these carried lined them up under that checkbox. */
  .note-person .problem { color: var(--text-dim); font-size: var(--text-small); margin: 2px 0 6px; }
  .note-person .choices { display: flex; flex-wrap: wrap; gap: 6px; }
  .note-person .choices button {
    background: var(--surface-alt); border: 1px solid var(--border); color: var(--text);
    border-radius: 10px; padding: 6px 10px; font-family: var(--font); font-size: var(--text-small);
  }
  .note-person .choices button:disabled { opacity: 0.5; }
  /* The unresolved name itself is the button — tapping it opens the picker. */
  .note-person .pick-name {
    background: var(--surface-alt); border: 1px dashed var(--accent);
    color: var(--text); border-radius: 10px; padding: 6px 10px;
    font-family: var(--font); font-size: var(--text-body);
  }
  /* One field per line: a cramped row of inputs is hard to fill in on a
     phone, which is where most notes get dictated. */
  .note-person .add-form {
    display: flex; flex-direction: column; gap: 8px;
    margin: 8px 0 0; padding: 12px; border-radius: 12px;
    background: var(--surface-alt); border: 1px solid var(--border);
  }
  .note-person .add-form .add-title { font-size: var(--text-small); font-weight: 600; }
  /* Client or referral partner, asked when the contact is created. Two
     buttons, not a text box: the partner group is the declared "Referral
     Partner" label and nothing else, so a box inviting "Realtor" would look
     like it answered the question while answering nothing. */
  .add-field { display: flex; flex-direction: column; align-items: center; gap: 4px; }
  .add-group { display: flex; gap: 6px; width: 100%; }
  .add-group button {
    flex: 1 1 0; background: var(--surface-alt); border: 1px solid var(--border);
    color: var(--text); border-radius: 10px; padding: 8px 10px;
    font-family: var(--font); font-size: var(--text-small);
  }
  .add-group button.picked {
    border-color: var(--accent); color: var(--accent); font-weight: 600;
  }
  .note-person .add-form label { display: flex; flex-direction: column; gap: 4px; }
  .note-person .add-form .field-label { font-size: var(--text-label); color: var(--text-dim); letter-spacing: 0.3px; }
  .note-person .add-form input {
    width: 100%; background: var(--surface);
    border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px;
    color: var(--text); font-family: var(--font); font-size: var(--text-heading);
  }
  .note-person .add-form input:focus { outline: none; border-color: var(--accent-think); }
  .note-person .add-form .add-actions { display: flex; gap: 8px; justify-content: flex-end; }
  .note-person .add-form .add-actions button {
    background: var(--surface); border: 1px solid var(--border); color: var(--text);
    border-radius: 10px; padding: 9px 14px; font-family: var(--font); font-size: var(--text-body);
  }
  .note-person .add-form .add-actions .primary {
    background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600;
  }
  .note-person .add-form .add-actions button:disabled { opacity: 0.5; }

  /* ===================== Sign-in gate ===================== */
  /*
   * The banner and the sign-in screen keep the brand green, on a white app.
   *
   * Not nostalgia: `logo.png` is a mint elephant and a **white** wordmark on
   * transparent, so on white the words disappear and only the elephant is
   * left. A recoloured mark is a branding decision, not a CSS one.
   *
   * It also happens to be the right look — a green band at the top, the work
   * on white below — and it honours "keep the top banner as is".
   */
  #signInScreen {
    position: fixed;
    inset: 0;
    background: var(--brand-green);
    color: #FFFFFF;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 18px;
    padding: 24px;
    text-align: center;
    z-index: 10;
  }

  #signInScreen.hidden { display: none; }

  #signInScreen h2 {
    margin: 0;
    font-size: var(--text-title);
    font-weight: 600;
  }

  #signInScreen p {
    margin: 0;
    font-size: var(--text-body);
    /* The page's dim grey is for white grounds; on the green it is mud. */
    color: rgba(255, 255, 255, 0.76);
    max-width: 280px;
  }

  #signInError {
    font-size: var(--text-small);
    color: var(--accent-live);
    min-height: 16px;
  }
  /* The one message here that carries somewhere to go. */
  #signInError a { color: var(--accent-live); }

  .oauth-btn:disabled { opacity: 0.5; }

  .oauth-btn {
    background: #FFFFFF;
    color: #1F1F1F;
    border: none;
    border-radius: 24px;
    padding: 12px 24px;
    font-size: var(--text-body);
    font-weight: 600;
    font-family: var(--font);
  }
  .oauth-btn:active { transform: scale(0.97); }

  /* The signed-in top bar. Sits in normal document flow (not fixed) so it
     never overlaps the header below it — it pushes the header down instead. */
  /*
   * White, like the rest of the app (owner, 2026-10-13), with the full-colour
   * mark on it — the dark-green wordmark that needs a light ground.
   *
   * The sign-in screen keeps the brand green and the white-wordmark
   * `logo.png`: it is a full-bleed branded moment rather than a working
   * surface, and it is the one place the reversed mark still belongs.
   */
  #topBar {
    width: 100%;
    padding: 12px 16px;
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    color: var(--text-dim);
    display: none;
    align-items: center;
    /* The mark on the left, everything else on the right, on one line
       (owner, 2026-09-29). The header below this used to hold the mark on its
       own centred row, which cost a whole band of screen. */
    justify-content: space-between;
    gap: 10px;
    flex: 0 0 auto;
  }
  .top-right { display: flex; align-items: center; gap: 10px; }
  #topBar.visible { display: flex; }

/* ===================== The sheet =====================
   A modal bottom sheet over a scrim (owner, 2026-10-30). The names are the
   standard ones and worth keeping: the panel is a *sheet*, the height it
   settles at is its *detent*, and the dimmed layer behind is the *scrim* —
   not an "overlay", which could be anything.

   Built on `<dialog>` + `showModal()`, so the scrim is the browser's own
   `::backdrop` and the screen behind is genuinely inert rather than merely
   dimmed. See the markup for what that buys. */
.sheet {
  /* `dialog` centres itself by default. `auto auto 0` pins it to the bottom
     edge and leaves the top free, which is what makes it a sheet. */
  margin: auto auto 0;
  padding: 0;
  border: 0;
  width: 100%;
  max-width: 100%;
  /* The detent: how far up it comes. Deliberately short of the full height —
     the band of dimmed screen above it is the whole point of the pattern,
     because it says what you were doing is still there underneath. */
  height: 85vh;
  max-height: 85vh;
  background: var(--bg);
  border-radius: 20px 20px 0 0;
  box-shadow: 0 -8px 40px rgba(23, 36, 28, 0.22);
  overflow: hidden;
  /* The home-indicator inset, once, here. Nothing inside may add it again —
     counting it twice is what cut the home screen's bottom row off on
     2026-10-28. */
  padding-bottom: env(safe-area-inset-bottom);
}
/* Where `dvh` exists it is the honest number: `vh` on a phone means the
   viewport with the browser chrome *retracted*, so a sheet sized in `vh`
   is taller than the screen while the address bar is showing. */
@supports (height: 85dvh) {
  .sheet { height: 85dvh; max-height: 85dvh; }
}

.sheet::backdrop {
  /* The scrim. Tinted to the brand's near-black rather than plain black, so
     it reads as this app dimming rather than a browser artefact. */
  background: rgba(23, 36, 28, 0.45);
  transition: background 240ms ease;
}
/* Pulled down to the peek. The scrim lifts most of the way, because the
   reason to move the sheet is to read what is behind it — and a note read
   through 45% of near-black is not read. It stays present, and the dialog
   stays modal, so the screen behind is still no more usable than before. */
.sheet.is-peek::backdrop { background: rgba(23, 36, 28, 0.10); }

/* Slide up from the edge it is anchored to. A sheet that fades in without
   moving reads as a dialog, which is a different promise: a dialog interrupts,
   a sheet is somewhere you went. */
.sheet[open] { animation: sheet-rise 220ms cubic-bezier(0.2, 0.8, 0.2, 1); }
.sheet[open]::backdrop { animation: scrim-in 220ms ease; }
@keyframes sheet-rise { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes scrim-in { from { opacity: 0; } to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  .sheet[open], .sheet[open]::backdrop { animation: none; }
}

/* The sheet itself is the column, so the inner is a flex item the dialog's
   own max-height can squeeze. `height: 100%` on the inner cannot do that job:
   against a fit-content parent the percentage is circular, so it falls back to
   the content height and overflows a capped sheet instead of scrolling inside
   it. Scoped to [open] deliberately — an unscoped `display: flex` would beat
   the browser's `display: none` for a closed dialog and leave it on screen. */
.sheet[open] { display: flex; flex-direction: column; }
.sheet-inner {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
}

/* The drag handle area. `touch-action: none` so a pull on the header moves
   the sheet instead of scrolling the page or triggering pull-to-refresh. */
.sheet-head, .sheet-grab { touch-action: none; }
.sheet-head { cursor: grab; }
.sheet-head:active { cursor: grabbing; }

.sheet-grab {
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: var(--border);
  margin: 10px auto 2px;
  flex: none;
}

.sheet-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 16px 10px;
  border-bottom: 1px solid var(--border);
  flex: none;
}
.sheet-title {
  margin: 0;
  flex: 1 1 auto;
  font-size: var(--text-heading);
  font-weight: 600;
  color: var(--text);
}
.sheet-close {
  flex: none;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: var(--surface);
  cursor: pointer;
}
.sheet-close svg { width: 18px; height: 18px; fill: var(--text-dim); }

/* The only thing that scrolls. The header stays put, so the close button is
   reachable however long the draft is. */
.sheet-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 14px 16px 18px;
}

/* --- what the email flow puts inside it --- */
/* The preset: one tap, no keyboard and nothing to say. Sized and bordered as
   the primary way in, because for most emails it is. */
.sheet-preset {
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  cursor: pointer;
  background: var(--surface);
  border: 1px solid var(--accent-brand);
  border-radius: 12px;
  padding: 12px 14px;
  margin-bottom: 14px;
}
.sheet-preset:active { background: var(--surface-alt); }
.sheet-preset:disabled { opacity: 0.5; }
.sheet-preset-title {
  display: block;
  font-size: var(--text-heading);
  font-weight: 600;
  color: var(--accent-brand);
}
.sheet-preset-note {
  display: block;
  margin-top: 2px;
  font-size: var(--text-small);
  color: var(--text-dim);
}
/* The quick emails, stacked. */
.sheet-quick { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.sheet-quick .sheet-preset { margin-bottom: 0; }
/* Said on the face of the button, because a `title` tooltip never appears on
   a phone. It does not disable anything: sending again is sometimes right. */
.sheet-preset-sent {
  display: block;
  margin-top: 2px;
  font-size: var(--text-small);
  color: var(--text-dim);
  font-weight: 400;
}
/* Copy, then open the thread. Laid out as a row so the two steps read in the
   order they are done. */
.sheet-sms-actions { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
.sheet-sms-actions button,
.sheet-sms-actions a {
  background: var(--surface); border: 1px solid var(--border); color: var(--text);
  border-radius: 8px; padding: 7px 12px; font-family: var(--font);
  font-size: var(--text-small); text-decoration: none; cursor: pointer;
}
.sheet-sms-actions a.primary {
  background: var(--accent); border-color: var(--accent);
  color: var(--on-accent); font-weight: 600;
}
.sheet-or {
  font-size: var(--text-small);
  color: var(--text-dim);
  text-align: center;
  margin-bottom: 10px;
}
.sheet-ask-label {
  display: block;
  font-size: var(--text-body);
  color: var(--text);
  margin-bottom: 10px;
}
.sheet-ask-row { display: flex; gap: 10px; align-items: flex-start; }
.sheet-ask-row textarea {
  flex: 1 1 auto;
  font-size: var(--text-body);
  font-family: inherit;
}
.sheet-mic {
  flex: none;
  width: 48px; height: 48px;
  display: grid; place-items: center;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  cursor: pointer;
}
.sheet-mic svg { width: 22px; height: 22px; fill: var(--accent-brand); }
.sheet-mic.is-live { background: var(--accent-live); border-color: var(--accent-live); }
.sheet-mic.is-live svg { fill: var(--on-accent); }
.sheet-note { font-size: var(--text-small); color: var(--text-dim); margin-top: 8px; }
.sheet-problem { font-size: var(--text-small); color: var(--accent-live); margin-top: 10px; }
.sheet-working { font-size: var(--text-body); color: var(--text-dim); padding: 18px 0; text-align: center; }

/* The text version, under the email. Copy rather than send: the app has no
   consent record for texting anyone, so it hands over the words and the
   loan officer sends it from their own phone. */
.sheet-sms { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); }
.sheet-sms-title { font-size: var(--text-label); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-dim); }
.sheet-sms-text {
  margin: 8px 0 10px;
  font-size: var(--text-body);
  color: var(--text);
  background: var(--surface);
  border-radius: 10px;
  padding: 10px 12px;
  white-space: pre-wrap;
}

/* ===================== Home =====================
   A welcome up top, six round buttons about three quarters of the way down.
   The empty middle is the point: it is thumb travel saved, and the room the
   greeting will grow into. */
#homeView {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  /* Greeting at the top, keys near the bottom, the gap between them doing the
     work of putting the keys under a thumb. */
  justify-content: space-between;
  /* `body` already pads for the home indicator, so adding the inset here as
     well counted it twice and spent it twice on the screen with least to
     spare. On a phone drawing edge to edge that is the navigation bar's
     height, twice, below keys that already sit on the bottom edge. This is
     what cut the owner's bottom row off on 2026-10-28: with the duplicate
     gone his home screen fits without needing the scroll below. */
  padding: 24px 20px 28px;
  min-height: 0;
  /* The keys are pinned to the bottom edge, so anything that makes this
     taller than the screen — a large system font, a short window — would put
     them out of reach rather than merely out of line. */
  overflow-y: auto;
}
#homeView[hidden] { display: none; }

/* 56px when the greeting was a single line; the quote beneath it now brings
   its own height, and every pixel here is taken from the gap that puts the
   keys under a thumb. */
.home-greeting { padding-top: 32px; text-align: center; }
.home-welcome {
  margin: 0;
  font-size: var(--text-display);
  font-weight: 600;
  color: var(--brand-green);
  line-height: 1.15;
}

/* The day's quote. Sized well down from the greeting on purpose — it is
   something to read while the thumb travels, not what the screen is for.
   Both tones are `--text-dim` (5.4:1 on white); the hierarchy comes from size
   and italics rather than from a paler grey, which would have failed. */
.home-quote {
  margin: 18px auto 0;
  /* A measure, not a width: ~34 characters is the comfortable line length,
     and it stops the quote running full width on a tablet. */
  max-width: 34ch;
  font-size: var(--text-body);
  line-height: 1.45;
  color: var(--text-dim);
}
.home-quote[hidden] { display: none; }
.home-quote-text { font-style: italic; }
.home-quote-source {
  display: block;
  margin-top: 6px;
  font-size: var(--text-small);
  font-style: normal;
}

.home-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 18px 12px;
  /* Two rows of three. On a wide screen it stays three across and centred
     rather than stretching into six-across, which would put the keys back out
     of reach of a thumb. */
  max-width: 420px;
  width: 100%;
  margin: 0 auto;
}

.home-key {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  background: none;
  border: none;
  padding: 0;
  font-family: var(--font);
  color: var(--text);
}

/* The mark itself, in brand green — no disc behind it (owner, 2026-10-13).
   The box stays a thumb-sized target even though nothing is drawn on it: the
   icon is what you aim at, but it is not what you have to hit. */
.home-key-dot {
  width: 68px; height: 68px;
  display: flex; align-items: center; justify-content: center;
  transition: transform 0.12s ease;
}
.home-key-dot svg { width: 42px; height: 42px; fill: var(--brand-green); }
.home-key:active .home-key-dot { transform: scale(0.92); }

.home-key-label {
  font-size: var(--text-small);
  font-weight: 600;
  text-align: center;
  /* "Referral Partners" is two words wide; the others are one. Wrapping is
     fine, a different row height is not — which is what happened when the
     scale went up and this height did not follow it. Two lines at the
     current step. */
  min-height: calc(var(--text-small) * 1.25 * 2);
  line-height: 1.25;
}

/* Seven destinations in a grid of three. The odd one out is centred rather
   than left hanging on its own in the first column, and `:last-child` keeps
   that true by itself if an eighth is ever added. */
.home-key:nth-child(7):last-child { grid-column: 2; }

/* ===================== Contacts view ===================== */
/* `main` is display:flex, which beats the browser's own rule for the hidden
   attribute — without this the hidden view keeps its space and overlaps the
   visible one. Applies to every element, so any future hidden view is safe. */
[hidden] { display: none !important; }

.search-bar { width: 100%; max-width: 480px; flex: 0 0 auto; }
#contactSearch {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 12px 14px;
  color: var(--text);
  font-family: var(--font);
  font-size: var(--text-heading);
}
#contactSearch::placeholder { color: var(--text-dim); }
#contactSearch:focus { outline: none; border-color: var(--accent-think); }

/* The list and the file both scroll inside the panel, not the page. */
.list-panel { overflow-y: auto; }
.list-panel[hidden] { display: none; }

.contact-row {
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--text-body);
  padding: 12px 2px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.contact-row:last-child { border-bottom: none; }

.back-btn {
  background: none;
  border: none;
  color: var(--text-dim);
  font-family: var(--font);
  font-size: var(--text-small);
  padding: 0 0 10px;
  text-align: left;
}

.file-name { font-size: var(--text-title); font-weight: 600; }
.file-detail { font-size: var(--text-small); color: var(--text-dim); margin-top: 2px; }
.file-section-label {
  font-size: var(--text-label); font-weight: 600; color: var(--text-dim);
  letter-spacing: 0.4px; margin: 16px 0 8px;
}
.file-empty { color: var(--text-dim); font-size: var(--text-small); }

/* The date in a history row. `.tp-row` carried the size and was deleted when
   touchpoints and notes merged, leaving this inheriting the browser's 16px —
   exactly the kind of gap a named scale would have caught. */
.tp-date { color: var(--text-dim); min-width: 52px; font-size: var(--text-small); }

.note-entry { border-top: 1px solid var(--border); padding: 12px 0; }
.note-entry:first-of-type { border-top: none; }
.note-entry .note-date { font-size: var(--text-small); color: var(--text-dim); }
.note-entry .note-head { font-size: var(--text-body); margin: 3px 0 0; }
.note-entry .note-detail { margin-top: 8px; font-size: var(--text-small); }
.note-entry .note-detail .recap-heading { margin-top: 10px; }
.note-entry .note-said {
  color: var(--text-dim); font-style: italic; white-space: pre-wrap; margin-top: 8px;
}
.note-entry .expand {
  background: none; border: none; color: var(--accent-think);
  font-family: var(--font); font-size: var(--text-small); padding: 6px 0 0;
}

.task-row {
  display: flex; gap: 8px; align-items: baseline;
  padding: 6px 0; border-bottom: 1px solid var(--border); font-size: var(--text-small);
}
.task-row:last-child { border-bottom: none; }
.task-row .due { color: var(--text-dim); font-size: var(--text-small); }

/* ===================== Ask about a contact =====================
   The answer never stands on its own: the notes it came from are printed
   underneath it, so a stretched reading is visible rather than trusted. */
.ask-bar { display: flex; gap: 8px; margin-bottom: 10px; }
#askInput,
#guidelineQuery {
  flex: 1 1 auto; min-width: 0;
  background: var(--surface-alt); border: 1px solid var(--border);
  border-radius: 12px; padding: 10px 12px;
  color: var(--text); font-family: var(--font); font-size: var(--text-heading);
}
#askInput::placeholder,
#guidelineQuery::placeholder { color: var(--text-dim); }
#askInput:focus,
#guidelineQuery:focus { outline: none; border-color: var(--accent-think); }
#askBtn,
#guidelineSearchBtn {
  flex: 0 0 auto; background: var(--surface-alt); border: 1px solid var(--border);
  border-radius: 12px; padding: 0 16px; color: var(--text);
  font-family: var(--font); font-size: var(--text-body); font-weight: 600;
}
#askBtn:disabled,
#guidelineSearchBtn:disabled { opacity: 0.5; }

#askAnswer,
#guidelineAnswer {
  background: var(--surface-alt); border: 1px solid var(--border);
  border-radius: 12px; padding: 12px 14px; margin-bottom: 12px; font-size: var(--text-body);
}
/* `overflow-wrap: anywhere` because this text is **quoted verbatim from
   somewhere else** and its word lengths are not ours to assume. A guideline
   passage is raw PDF extraction: HUD handbooks carry long URLs, defined terms
   and table rows that come out as one unbroken string, and a single one of
   those pushes the whole phone screen sideways. Caught at 375px with a
   52-character token, 2026-10-27. Costs nothing for ordinary prose. */
#askAnswer .answer-text,
#guidelineAnswer .answer-text {
  margin: 0 0 4px; line-height: 1.5; overflow-wrap: anywhere;
}
#askAnswer .answer-none,
#guidelineAnswer .answer-none { color: var(--text-dim); }
#askAnswer .sources-label,
#guidelineAnswer .sources-label {
  font-size: var(--text-label); font-weight: 600; color: var(--text-dim);
  letter-spacing: 0.4px; margin: 12px 0 4px;
}
#askAnswer .source,
#guidelineAnswer .source { border-top: 1px solid var(--border); padding-top: 8px; margin-top: 8px; }
#askAnswer .source:first-of-type,
#guidelineAnswer .source:first-of-type { border-top: none; }

/* Where a note was filed, and the one-tap questions when the app can't know.
   A file is never opened silently — see "Note subjects, client profiles and
   deals" in ARCHITECTURE.md. */
.filing {
  border-top: 1px solid var(--border);
  margin-top: 10px;
  padding-top: 10px;
  font-size: var(--text-small);
}
.filing .choices { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.filing .choices button {
  background: var(--surface-alt); border: 1px solid var(--border); color: var(--text);
  border-radius: 10px; padding: 8px 12px; font-family: var(--font); font-size: var(--text-small);
}
.filing .choices button.pick-name { border-style: solid; border-color: var(--accent); }
.filing .choices button:disabled { opacity: 0.5; }

/* ===================== Result sections =====================
   Four boxes instead of one long column (owner, 2026-09-22): the note, the
   people, the tasks, the calendar. Anything needing attention opens with a
   warning mark; anything done collapses behind a tick, so the actionable
   parts are never below the fold. */
.sec {
  border: 1px solid var(--border);
  border-radius: 12px;
  margin-bottom: 8px;
  background: var(--surface-alt);
  overflow: hidden;
}
.sec:last-child { margin-bottom: 0; }
.sec-action { border-color: var(--accent); }

.sec-head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  background: none;
  border: none;
  padding: 12px 14px;
  color: var(--text);
  font-family: var(--font);
  font-size: var(--text-body);
  text-align: left;
}

.sec-mark {
  flex: 0 0 auto;
  width: 20px; height: 20px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--text-small); font-weight: 700;
  /* A wash of the done colour. Heavier than it was on dark: a 15% tint
     that read as a soft glow against deep green is invisible on white. */
  background: rgba(47,107,72,0.12);
  color: var(--accent-done);
}
/* Filled rather than tinted. A gold glyph on a gold wash measured 3.9:1 on
   white — under the bar, and this is the one mark whose whole job is being
   noticed. On the old dark ground the wash gave it the contrast; on white
   it took it away. */
.sec-action .sec-mark { background: var(--accent); color: var(--on-accent); }

.sec-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.sec-title { font-weight: 600; }
.sec-state { font-size: var(--text-small); color: var(--text-dim); }
.sec-action .sec-state { color: var(--accent); }
.sec-chev { flex: 0 0 auto; color: var(--text-dim); font-size: var(--text-small); }

.sec-body { padding: 0 14px 12px; }
.sec-body[hidden] { display: none; }
.sec-hint { font-size: var(--text-label); color: var(--text-dim); letter-spacing: 0.3px; margin-bottom: 4px; }
/* A hint inside a section body — the deal card's explanation, the Tasks
   "nothing was asked for" line. Same dim, small voice as `.note-person .how`,
   where the class started: without a rule it inherits the card's body size at
   full brightness and reads as content rather than as guidance. */
.sec-body .how { font-size: var(--text-small); color: var(--text-dim); }
.sec-body .note-person:first-of-type { border-top: none; }
.sec-body .filing { margin-top: 8px; }

/* ===================== Picking a contact =====================
   Was the recap picker on the Talk screen; the owner split that door into
   Deals and Referral Partners, and opening a new deal asks the same two
   questions — who is the client, and who referred them. */
.pick-panel { display: flex; flex-direction: column; gap: 10px; }
.pick-panel input[type="search"] {
  width: 100%;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 10px; padding: 10px 12px;
  color: var(--text); font-family: var(--font);
  /* The page's own body size (owner, 2026-10-14: it read bigger than
     everything around it).

     It used to be held at --text-heading because iOS zooms the page when an
     input is under 16px — but this app's viewport declares
     `maximum-scale=1, user-scalable=no`, so that zoom cannot happen here at
     all. The rule was real and did not apply, and several comments in this
     file repeated it. */
  font-size: var(--text-body);
}
.pick-results { display: flex; flex-direction: column; gap: 6px; }
.pick-hint { font-size: var(--text-small); color: var(--text-dim); }

.pick-row {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 10px; padding: 9px 12px;
  color: var(--text); font-family: var(--font); font-size: var(--text-small);
  text-align: left;
}
.pick-row-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pick-row-name { font-weight: 600; font-size: var(--text-small); }
.pick-row-detail { font-size: var(--text-label); color: var(--text-dim); }
/* Which group they are in, because it decides whether this becomes a client
   note or a relationship note. */
.pick-row-group {
  flex: 0 0 auto;
  font-size: var(--text-label); letter-spacing: 0.3px;
  color: var(--text-dim);
}

.pick-ask {
  display: flex; flex-direction: column; gap: 6px;
  border: 1px solid var(--accent); border-top: none;
  border-radius: 0 0 10px 10px;
  padding: 10px 12px; margin-top: -6px;
  background: var(--surface-alt);
}
.pick-ask-label { font-size: var(--text-small); color: var(--text-dim); }

.pick-add {
  width: 100%;
  background: var(--surface-alt); border: 1px dashed var(--accent-brand);
  border-radius: 10px; padding: 10px 12px;
  color: var(--text); font-family: var(--font); font-size: var(--text-small);
}
.pick-panel .add-form {
  display: flex; flex-direction: column; gap: 8px; margin-top: 10px;
  padding: 12px; border-radius: 12px;
  background: var(--surface-alt); border: 1px solid var(--border);
}
.pick-panel .add-form .add-title { font-size: var(--text-small); font-weight: 600; }
.pick-panel .add-form label { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.pick-panel .add-form input {
  width: 100%; background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 9px 12px;
  color: var(--text); font-family: var(--font);
  font-size: var(--text-heading);
}
.pick-panel .add-actions { display: flex; gap: 8px; justify-content: flex-end; }
.pick-panel .add-actions button {
  background: none; border: 1px solid var(--border); color: var(--text-dim);
  border-radius: 10px; padding: 9px 14px;
  font-family: var(--font); font-size: var(--text-small);
}
.pick-panel .add-actions button.primary {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600;
}
.pick-panel .add-actions button:disabled { opacity: 0.5; }

.pick-actions { display: flex; gap: 10px; align-items: center; }
.pick-skip {
  background: none; border: 1px solid var(--border); color: var(--text-dim);
  border-radius: 10px; padding: 7px 14px;
  font-family: var(--font); font-size: var(--text-small);
}
.pick-cancel {
  align-self: flex-start;
  background: none; border: none; color: var(--text-dim);
  font-family: var(--font); font-size: var(--text-small);
  padding: 4px 0;
}

/* While a result is on screen the mic is not the point any more: it shrinks
   so the boxes get the space. On a phone this was the difference between 180
   and ~400px of readable card. Tapping it still starts a new dictation. */
.assistant-label {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  margin-top: 10px;
}
.assistant-title { font-size: var(--text-body); font-weight: 600; color: var(--text); }
.assistant-hint { font-size: var(--text-small); color: var(--text-dim); }
/* Once a result is on screen the assistant is not the point any more, and the
   boxes need the room. The mic shrinks here too. */
body.note-open .assistant-label { display: none; }
body.note-open .mic-zone { width: 72px; height: 72px; margin-top: 0; }
body.note-open #micBtn { width: 58px; height: 58px; }
body.note-open #micBtn svg { width: 26px; height: 26px; }
body.note-open .ring { display: none; }
body.note-open main { gap: 12px; padding-top: 12px; }

/* The dictated-contact form sits inside a result box rather than a person row */
.sec-body .add-form {
  display: flex; flex-direction: column; gap: 8px; margin: 8px 0 0;
  padding: 0; background: none; border: none;
}

/* A drafted email is read and edited before it goes anywhere, so the body is
   a real editing surface rather than a preview. */
.draft-body {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  color: var(--text);
  font-family: var(--font);
  /* The same 14px as the fields above it and the card around it: one email,
     one size. */
  font-size: var(--text-body);
  line-height: 1.5;
  resize: vertical;
}
.draft-body:focus { outline: none; border-color: var(--accent-think); }

/* ===================== Brand marks ===================== */
/* The white-and-mint logo, for the dark green ground. */
.brand-logo { width: min(260px, 70vw); height: auto; margin-bottom: 4px; }
.brand-mark { height: 30px; width: auto; }

header { display: flex; align-items: center; justify-content: center; }

/* Mint rather than gold for the mic: it is the app's resting state, and gold
   is reserved for things asking for attention. */
#micBtn svg { fill: var(--brand-mint); }
.ring { border-color: var(--accent-brand); }

/* ===================== Needs attention ===================== */
#attentionView[hidden] { display: none; }


.todo-group { margin-bottom: 14px; }
.todo-group-label {
  font-size: var(--text-label); font-weight: 600; color: var(--text-dim);
  letter-spacing: 0.4px; margin: 10px 0 6px;
}
.todo-item {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 10px 12px;
  margin-bottom: 8px;
  background: var(--surface-alt);
}
.todo-item .todo-line { font-size: var(--text-body); }
.todo-item .todo-detail { font-size: var(--text-small); color: var(--text-dim); margin-top: 2px; }
.todo-item .choices { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.todo-item .choices button {
  background: var(--surface); border: 1px solid var(--border); color: var(--text);
  border-radius: 10px; padding: 7px 11px; font-family: var(--font); font-size: var(--text-small);
}
.todo-item .choices button.primary {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600;
}
.todo-item .choices button:disabled { opacity: 0.5; }
/* Why the button did nothing, beside the button. `setStatus` writes to the
   Assistant screen, which is not the screen this list is on, so a failed
   action here used to explain itself where nobody could see it. Named and
   coloured like `.sheet-problem`, which solved the same thing in the sheet. */
.todo-item .todo-problem,
/* The same voice for "this cannot be undone" as for "that did not work":
   both are the app warning rather than describing, and one colour for the
   two keeps the row from looking like it has two kinds of trouble. */
.todo-warn {
  font-size: var(--text-small); color: var(--accent-live); margin-top: 8px;
}
/* A destructive confirmation, filled rather than outlined — it is the one
   button in the app that cannot be taken back, and it should not look like
   the ones that can. White on #C0442A measures 5.1:1, so 13px is legible. */
.todo-item .choices button.danger,
.note-admin .choices button.danger {
  background: var(--accent-live); border-color: var(--accent-live);
  color: #fff; font-weight: 600;
}

/* Deleting a note, under the words themselves inside an open note. */
.note-admin { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); }
.note-admin .choices { display: flex; flex-wrap: wrap; gap: 6px; }
.note-admin .choices button {
  background: var(--surface); border: 1px solid var(--border); color: var(--text);
  border-radius: 10px; padding: 7px 11px; font-family: var(--font); font-size: var(--text-small);
}
.note-admin .choices button:disabled { opacity: 0.5; }
.note-admin .todo-problem { font-size: var(--text-small); color: var(--accent-live); margin-top: 8px; }
.todo-clear { color: var(--text-dim); font-size: var(--text-small); padding: 8px 0; }

/* Recovered dictation: the note survived, the way to send it has to exist. */
.recovered {
  display: flex; flex-wrap: wrap; gap: 6px;
  /* The transcript above clips mid-line when it overflows, so the buttons
     need a visible edge of their own or they read as part of the text. */
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);
}
.recovered[hidden] { display: none; }
.recovered button {
  background: var(--surface); border: 1px solid var(--border); color: var(--text);
  border-radius: 10px; padding: 8px 12px; font-family: var(--font); font-size: var(--text-small);
}
.recovered button.primary {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600;
}

/* ===================== Top bar: attention bell, account =====================
   Both are icons. The bell is absent unless something is waiting, so its
   presence is the message — a permanent tab for an empty list teaches you to
   stop looking at it (owner, 2026-09-24). */
.icon-btn {
  position: relative;
  background: none; border: none; padding: 4px;
  color: var(--accent-brand);
  display: flex; align-items: center; justify-content: center;
}
.icon-btn[hidden] { display: none; }
.icon-btn .icon { width: 24px; height: 24px; fill: currentColor; }
/* Gold while the To do list is the screen you are on. */
.icon-btn.active { color: var(--accent); }

.icon-badge {
  position: absolute; top: -1px; right: -3px;
  min-width: 17px; padding: 0 4px;
  border-radius: 9px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: var(--text-label); font-weight: 700; line-height: 17px;
  text-align: center;
  /* Against the bell rather than beside it, so the count reads as part of
     the icon at a glance. */
  border: 2px solid var(--bg);
}
.icon-badge[hidden] { display: none; }

.profile { position: relative; }
.avatar {
  width: 30px; height: 30px; padding: 0;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--accent-brand);
  font-family: var(--font); font-size: var(--text-small); font-weight: 600;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.avatar img[hidden] { display: none; }

.profile-drop {
  position: absolute; top: 38px; right: 0; z-index: 20;
  /* Wide enough for a company email on one line: breaking
     name@leadershipmortgage.com across two reads as a mistake. */
  min-width: 216px;
  /* Hangs below the band and over the white page, so it is a page panel. */
  background: #FFFFFF;
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 6px;
  display: flex; flex-direction: column; gap: 2px;
  box-shadow: 0 10px 24px rgba(23, 36, 28, 0.14);
}
.profile-drop[hidden] { display: none; }
.profile-email {
  font-size: var(--text-label); color: var(--text-dim);
  padding: 6px 8px 8px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
  overflow-wrap: anywhere;
}
.profile-drop button {
  background: none; border: none; color: var(--text);
  font-family: var(--font); font-size: var(--text-small);
  text-align: left; padding: 9px 8px; border-radius: 8px;
}
.profile-drop button:active { background: var(--surface-alt); }

/* ===================== Today =====================
   The calendar is context; the reach-outs are the reason to open the screen
   (owner, 2026-09-28), so the calendar collapses and the list below does not. */
.panel-toggle {
  width: 100%; background: none; border: none; padding: 0;
  display: flex; align-items: center; justify-content: space-between;
  font-family: var(--font); cursor: pointer;
}
.panel-toggle .chev {
  color: var(--text-dim); font-size: var(--text-small); line-height: 1;
  transition: transform 0.15s ease;
}
.panel-toggle[aria-expanded="false"] .chev { transform: rotate(-90deg); }

.appt { display: flex; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border); }
.appt:last-child { border-bottom: none; }
.appt-time {
  flex: 0 0 62px; font-size: var(--text-small); font-weight: 600; color: var(--accent-brand);
  padding-top: 1px;
}
.appt-title { font-size: var(--text-body); }
.appt-detail { font-size: var(--text-small); color: var(--text-dim); margin-top: 2px; }

/* Two groups, and the colour is never the only thing saying which is which —
   each group keeps its heading, for anyone who can't tell them apart. */
.reach-group-label {
  font-size: var(--text-label); font-weight: 600; color: var(--text-dim);
  letter-spacing: 0.4px; margin: 12px 0 6px;
}
.reach-group-label:first-child { margin-top: 0; }

.reach {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; margin-bottom: 6px;
  background: var(--surface-alt); border-radius: 10px;
  border-left: 3px solid var(--border);
}
.reach.partner { border-left-color: var(--accent-brand); }
.reach.client { border-left-color: var(--accent); }
.reach-body { flex: 1 1 auto; min-width: 0; }
.reach-name { font-size: var(--text-body); }
.reach-detail { font-size: var(--text-small); color: var(--text-dim); margin-top: 2px; }
.reach-call {
  flex: 0 0 auto;
  background: var(--surface); border: 1px solid var(--border); color: var(--accent-brand);
  border-radius: 10px; padding: 7px 11px;
  font-family: var(--font); font-size: var(--text-small); text-decoration: none;
}

/* The Talk screen's transcript wants to fill whatever room is left; these two
   want to be as tall as what's in them, so the list starts where the calendar
   ends rather than after a field of empty green. The reach-out list keeps the
   remaining space, because it is the half that grows. */
#todayView .panel { flex: 0 0 auto; }
#todayView .panel:last-child { flex: 1 1 auto; }
#calendarList { overflow-y: auto; }
/* Capped rather than left to grow. The owner's view is that a long task list
   is a list to clean up (2026-10-27), but a bad week must not push the
   reach-outs off the screen — so this scrolls inside its own panel and the
   whole section folds away. */
#taskList { overflow-y: auto; max-height: 45vh; }

/* ===================== Tasks =====================
   One ordered list, four groups: overdue, due today, due in future, then
   everything undated (owner, 2026-10-27). A tick is a real write to Google,
   so it is a button rather than a checkbox input: it can fail, and a checkbox
   that ticks itself and then silently un-ticks is worse than one that waits.
   Subtasks are indented under their parent and move with it. */
.task-group-label {
  font-size: var(--text-label); font-weight: 600; color: var(--text-dim);
  letter-spacing: 0.4px; margin: 12px 0 6px;
}
.task-group-label:first-child { margin-top: 0; }
.task-group-label.late { color: var(--accent); }

.task-item {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 8px 0; border-bottom: 1px solid var(--border);
}
.task-item:last-child { border-bottom: none; }
/* A subtask reads as part of the thing above it, not as a task of its own. */
.task-item.subtask { padding-left: 22px; }

.task-tick {
  flex: 0 0 auto; width: 22px; height: 22px; margin-top: 1px;
  background: var(--surface); border: 1.5px solid var(--border);
  border-radius: 50%; padding: 0;
  font-family: var(--font); font-size: var(--text-label); line-height: 1;
  color: var(--accent-brand); cursor: pointer;
}
.task-tick:disabled { opacity: 0.4; }
.task-tick.done { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

/* The title is a button because tapping it opens the editor. Styled as text:
   a row that looks like a button but only renames things would be a lie about
   what it does. */
.task-open {
  display: block; width: 100%; text-align: left;
  background: none; border: none; padding: 0;
  color: var(--text); font-family: var(--font); font-size: var(--text-body);
  line-height: 1.35; cursor: pointer; overflow-wrap: anywhere;
}
.task-item-body { flex: 1 1 auto; min-width: 0; }
.task-item-detail {
  font-size: var(--text-small); color: var(--text-dim); margin-top: 2px;
  overflow-wrap: anywhere;
}
/* Lateness is the part that changes what you do next, so it carries the
   accent while the rest of the line stays quiet. */
.task-late { color: var(--accent); }

.task-stale {
  font-size: var(--text-small); color: var(--text-dim);
  padding: 10px 0 0; margin-top: 8px; border-top: 1px solid var(--border);
}

/* Reword it, or move the date. A date and never a time: Google Tasks holds a
   date only, and a field that accepts 2:30pm and drops it is worse than one
   that never asked. */
.task-edit { display: flex; flex-direction: column; gap: 6px; width: 100%; }
.task-edit-note { font-size: var(--text-label); color: var(--text-dim); }
.task-edit-problem { font-size: var(--text-small); color: var(--accent); }

/* A promise the loan officer made, and the day has come. Louder than an
   elapsed interval on purpose: this is the kind of lateness the person on the
   other end can feel. */
.promised-label { color: var(--accent); }
.reach.promised {
  border-left-color: var(--accent);
  border: 1px solid var(--accent);
  border-left-width: 3px;
}
.reach.promised .reach-name { font-weight: 600; }

.appt-link { text-decoration: none; color: inherit; }
.appt-link:active { opacity: 0.7; }

/* The only button in the app that cannot be undone. It carries the recipient
   in its own label, and sits furthest from Discard. */
.send-btn { max-width: 100%; overflow-wrap: anywhere; }
.send-btn:disabled { opacity: 0.5; }


/* The draft and new-contact cards' buttons never had a rule of their own — the
   only one was scoped to the picker inside a note — so they sat flush against
   each other with no gap.
   
   Kept deliberately small. The first version of this used the same padding and
   type size as the buttons elsewhere in the app, which turned a 21px row into
   a 96px one — a quarter of the card's visible height spent on buttons instead
   of the message they act on (owner, 2026-09-28). The card scrolls, so every
   pixel here is a pixel of the draft you cannot see. */
/* The sheet's own first step puts an action row straight into the body
   rather than inside an .add-form, so it is named here too. Without this the
   browser drew "Write it" as a bare default button beside a carefully
   styled preset. */
.add-form .add-actions {
  display: flex; flex-wrap: wrap; gap: 6px;
  justify-content: flex-end; margin-top: 2px;
}
/* The sheet's own action row — "Write it" under the field. Centred, and
   given room to breathe away from the bottom of the textarea, which it was
   sitting right against (owner, 2026-10-30). */
.sheet-body > .add-actions {
  display: flex; flex-wrap: wrap; gap: 6px;
  justify-content: center; margin-top: 18px;
}
/* Discard, Save Draft and Send on one row (owner, 2026-10-30). The labels are
   short enough now that wrapping only happens on the narrowest phones, and a
   lone Send on a second row reads as a different, more serious button than
   the one that was meant. */
.sheet-body .add-form .add-actions { flex-wrap: nowrap; }
.sheet-body .add-form .add-actions button { white-space: nowrap; flex: 0 1 auto; min-width: 0; }
.add-form .add-actions button,
.sheet-body > .add-actions button {
  background: var(--surface); border: 1px solid var(--border); color: var(--text);
  border-radius: 8px; padding: 5px 9px; font-family: var(--font); font-size: var(--text-small);
}
.sheet-body > .add-actions button { padding: 9px 22px; font-size: var(--text-body); }
.add-form .add-actions .primary,
.sheet-body > .add-actions .primary {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600;
}
.add-form .add-actions button:disabled,
.sheet-body > .add-actions button:disabled { opacity: 0.5; }

/* TO and SUBJECT had no rule of their own, so the browser drew them white, in
   its own font, directly above a MESSAGE box that is dark green and Poppins
   (owner, 2026-09-29). Same box, same type, same field. */
.add-form input {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  color: var(--text);
  font-family: var(--font);
  /* 14px, matching the card around it. These were 16px to stop iOS zooming
     the page on focus, which made them the largest text on the screen --
     larger than the transcript, in a card whose own type is 14 (owner,
     2026-09-29). The viewport meta already pins maximum-scale, so the zoom
     this guarded against is mostly handled there. */
  font-size: var(--text-body);
  line-height: 1.4;
}
.add-form input::placeholder { color: var(--text-dim); }
.add-form input:focus { outline: none; border-color: var(--accent-think); }

/* ===================== Market =====================
   Numbers first and large, because this screen is glanced at rather than
   read. Everything carries the date it belongs to: a yield without a date is
   indistinguishable from a stale one. */
/* **Market scrolls inside the shell, and the shell still does not scroll.**
   Adding the news section on 2026-11-04 made this necessary and briefly got
   it wrong: the three rate panels already fill a phone, so a fourth panel
   pushed the document past the viewport and the whole page began to scroll
   — the exact thing `CLAUDE.md` warns makes anything pinned to the shell's
   bottom edge unreachable.

   The fix is the pattern `.crm-board` already uses: this screen is the
   scroll container, with `min-height: 0` so it is allowed to be shorter than
   its content, and every panel keeping its natural height inside it. The
   last panel no longer stretches to fill — with a scrolling screen there is
   no slack left to absorb, and stretching the news panel would have given
   the headlines a scrollbar of their own inside a screen that already had
   one. */
#marketView {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  justify-content: flex-start;
}
#marketView .panel { flex: 0 0 auto; }

.rate-big { font-size: var(--text-display); font-weight: 600; color: var(--accent-brand); line-height: 1.1; }
.rate-move { font-size: var(--text-body); margin-top: 4px; }
.rate-move.up { color: var(--accent); }
.rate-move.down { color: var(--accent-brand); }
.rate-move.flat { color: var(--text-dim); }
.rate-asof { font-size: var(--text-label); color: var(--text-dim); margin-top: 6px; }

.rate-pair { display: flex; gap: 22px; margin-top: 2px; }
.rate-one .rate-label { font-size: var(--text-label); color: var(--text-dim); letter-spacing: .4px; }
.rate-one .rate-value { font-size: var(--text-title); font-weight: 600; color: var(--text); }

/* The survey is a weekly average, not a quote. Said on the screen, not just
   in the code comments, because this is the number most likely to be read
   aloud to somebody. */
.rate-caveat {
  font-size: var(--text-label); color: var(--text-dim); line-height: 1.4;
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);
}

.event-row { display: flex; justify-content: space-between; gap: 12px; padding: 7px 0; }
.event-row + .event-row { border-top: 1px solid var(--border); }
.event-name { font-size: var(--text-body); }
.event-when { font-size: var(--text-small); color: var(--text-dim); white-space: nowrap; }

/* ===================== Industry news =====================
   Rows under the Market numbers. Each one is a whole anchor rather than a
   div with a link in it: the target on a phone is the row, and a headline
   you have to hit exactly is a headline you do not open.

   Every size here comes off the scale — label for the source line, body for
   the headline, small for the summary. Nothing sets a pixel of its own. */
.news-row {
  display: block;
  padding: 11px 0;
  color: inherit;
  text-decoration: none;
}
.news-row + .news-row { border-top: 1px solid var(--border); }
.news-row:first-child { padding-top: 2px; }

.news-meta {
  display: flex; align-items: baseline; gap: 8px;
  margin-bottom: 3px;
}
.news-source {
  font-size: var(--text-label); font-weight: 600;
  letter-spacing: .4px; text-transform: uppercase;
  color: var(--accent-brand);
}
.news-when {
  font-size: var(--text-label); color: var(--text-dim);
  white-space: nowrap;
}

.news-title {
  font-size: var(--text-body); font-weight: 600;
  line-height: 1.35; color: var(--text);
}
.news-summary {
  font-size: var(--text-small); color: var(--text-dim);
  line-height: 1.45; margin-top: 3px;
}

/* A tap needs to look like it landed. There is no hover on a phone and the
   page it opens is a new tab, so the row says so for the moment it is held. */
.news-row:active .news-title { color: var(--accent-brand); }
@media (hover: hover) {
  .news-row:hover .news-title { color: var(--accent-brand); }
}

/* ===================== Client profile =====================
   The one part of the contact file the model wrote rather than the loan
   officer, so every line carries the notes behind it. */
.profile-block { margin-top: 10px; }
.profile-heading {
  font-size: var(--text-label); font-weight: 600; color: var(--accent-brand);
  letter-spacing: .4px; text-transform: uppercase; margin: 10px 0 4px;
}
.profile-line { padding: 5px 0; border-bottom: 1px solid var(--border); }
.profile-line:last-of-type { border-bottom: none; }
.profile-text { font-size: var(--text-body); line-height: 1.45; }
.profile-source {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  font-size: var(--text-label); color: var(--text-dim); margin-top: 2px;
}
/* Not `.profile-drop`: that class is the account menu in the top bar, which
   is absolutely positioned, so sharing it sent every Remove button to the top
   of the screen. */
.profile-remove {
  background: none; border: none; color: var(--text-dim);
  font-family: var(--font); font-size: var(--text-label); text-decoration: underline;
  padding: 2px 0; flex: 0 0 auto;
}
.profile-remove:disabled { opacity: 0.5; }
.profile-built { font-size: var(--text-label); color: var(--text-dim); margin-top: 8px; }
.profile-actions { margin-top: 8px; }
.profile-actions .primary {
  background: var(--accent); border: 1px solid var(--accent); color: var(--on-accent);
  font-weight: 600; border-radius: 8px; padding: 5px 9px;
  font-family: var(--font); font-size: var(--text-small);
}
.profile-actions .primary:disabled { opacity: 0.6; }

/* ===================== Contact history =====================
   One dated list, each row opening its own note, instead of a touchpoint list
   and a note list saying much the same thing twice. */
.history-entry { border-bottom: 1px solid var(--border); }
.history-entry:last-child { border-bottom: none; }
.history-row { display: flex; align-items: baseline; gap: 10px; padding: 6px 0; }
/* Level with the date beside it and the detail under it (owner,
   2026-10-15). It was a step larger, which made every entry in the list look
   like a heading over its own contents. Colour already separates it: the date
   is dim and this is not. */
.history-type { flex: 1 1 auto; font-size: var(--text-small); }
.history-notes {
  flex: 0 0 auto;
  background: none; border: 1px solid var(--border); color: var(--text-dim);
  border-radius: 8px; padding: 3px 9px;
  font-family: var(--font); font-size: var(--text-label);
}
.history-notes.open { color: var(--accent-brand); border-color: var(--accent-brand); }
.history-entry .note-detail { padding: 2px 0 10px; }


/* An open note inside the history was rendering at the browser's default 16px,
   because nothing had ever sized it, on a page set at 10–12 (owner,
   2026-09-29). */
.history-entry .note-detail { font-size: var(--text-small); line-height: 1.5; }
.history-entry .note-head { font-size: var(--text-small); margin: 0 0 6px; }
.history-entry .recap-heading {
  font-size: var(--text-label); font-weight: 600; color: var(--accent);
  letter-spacing: .2px; margin: 8px 0 2px;
}
.history-entry .recap-body { font-size: var(--text-small); margin: 0; white-space: pre-wrap; }
.history-entry .note-commitment { font-size: var(--text-small); margin: 6px 0; }
.history-entry .note-commitment strong { color: var(--accent); font-weight: 600; }
.history-entry .note-said {
  font-size: var(--text-small); color: var(--text-dim); font-style: italic;
  margin-top: 8px; white-space: pre-wrap;
}

/* A section header that opens and shuts. */
.section-toggle {
  width: 100%; background: none; border: none; padding: 0;
  display: flex; align-items: center; justify-content: space-between;
  font-family: var(--font); cursor: pointer;
}
.section-toggle .chev { color: var(--text-dim); font-size: var(--text-label); }
.section-toggle .file-section-label { margin: 16px 0 8px; }

/* ===================== Contact groups =====================
   Clients / Partners / Everyone. The count rides on the chip so the choice is
   informed before it is made — "Partners 8" says what is behind the tap. */
.chip-row { display: flex; gap: 6px; flex: 0 0 auto; width: 100%; max-width: 480px; }
.chip {
  flex: 1 1 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-dim);
  font-family: var(--font);
  font-size: var(--text-small);
  padding: 6px 4px;
  display: flex; align-items: center; justify-content: center; gap: 5px;
}
.chip[aria-pressed="true"] {
  background: var(--brand-mint);
  border-color: var(--accent-brand);
  color: var(--brand-green);
  font-weight: 600;
}
.chip .chip-count { font-size: var(--text-label); opacity: 0.75; }

/* ===================== Contacts: three tabs and a + =====================
   Built 2026-11-05. The + sits in the row with the tabs rather than above or
   below it because adding somebody is the fourth thing you do with this
   list, not a different kind of act — and it is round like the tabs for the
   same reason. It is outside #contactGroups on purpose: every loop in app.js
   that reads the tabs asks that element for its .chip children, and a + in
   there would have been counted as a fourth group. */
.contact-tabs {
  display: flex; gap: 6px; align-items: stretch;
  flex: 0 0 auto; width: 100%; max-width: 480px;
}
.contact-tabs .chip-row { flex: 1 1 auto; width: auto; max-width: none; }
.chip-add {
  flex: 0 0 auto; width: 36px;
  background: var(--surface); border: 1px solid var(--accent-brand);
  border-radius: 999px; color: var(--accent-brand);
  font-family: var(--font); font-size: var(--text-heading); font-weight: 600;
  line-height: 1; padding: 0;
}
.chip-add:active { background: var(--brand-mint); }

/* ===================== A contact, in the sheet =====================
   Name, phone, email, company and label — the five things the owner asked
   this screen to carry (2026-11-05). A label column rather than a run-on
   line of dot-separated details: the old one-line version could not say
   which of two numbers was missing, and "not on file" needs a field to be
   absent from. */
.contact-facts {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 10px 12px;
}
.contact-fact {
  display: flex; gap: 10px; align-items: baseline;
  padding: 6px 0; border-top: 1px solid var(--border);
  color: var(--text); text-decoration: none;
}
/* The rule between facts, never above the first one — which is the second
   child now that the pencil's row sits at the top of the card. */
.contact-fact:first-child,
.contact-facts-head + .contact-fact { border-top: none; }
.contact-fact-label {
  flex: 0 0 62px;
  font-size: var(--text-label); letter-spacing: .4px; color: var(--text-dim);
}
/* `overflow-wrap: anywhere` because an email address has no spaces in it and
   a long one would otherwise push the card wider than the phone. */
.contact-fact-value {
  flex: 1 1 auto; min-width: 0;
  font-size: var(--text-body); overflow-wrap: anywhere;
}
/* A real tel: link, because this is a screen opened on a phone about
   somebody you are about to ring. */
a.contact-fact .contact-fact-value { color: var(--accent-think); }
.contact-fact-none { flex: 1 1 auto; font-size: var(--text-small); color: var(--text-dim); }
.contact-fact-note { font-size: var(--text-small); color: var(--text-dim); margin-top: 6px; }

/* The form, in the sheet. The same .add-form the dictated-contact card uses
   — the owner's standing rule from 2026-11-05 is that a new screen takes the
   shape of the ones already there — but those layout rules were scoped to
   .note-person, so the sheet needs its own copy of them. Left-aligned
   captions here: the note card centres its fields inside a narrow column,
   and the sheet is the width of the screen. */
.sheet-body .add-form { display: flex; flex-direction: column; gap: 10px; }
.sheet-body .add-form .add-title { font-size: var(--text-small); color: var(--text-dim); }
.sheet-body .add-form label { display: flex; flex-direction: column; gap: 4px; }
.sheet-body .add-form .field-label {
  font-size: var(--text-label); color: var(--text-dim); letter-spacing: 0.3px;
}
.sheet-body .add-field { align-items: stretch; }
.sheet-body .ask-bar { margin-top: 16px; }

/* Said once, under a list that stopped short or widened past the chip. */
.list-note { font-size: var(--text-small); color: var(--text-dim); padding: 8px 2px 2px; }

/* ===================== Calculator =====================
   Three fields and one answer, sized for thumbs. The answer leads because it
   is the only thing on the screen you are actually looking at. */
.calc-answer { padding: 4px 0 14px; }
.calc-total {
  font-size: var(--text-display); font-weight: 600; color: var(--accent);
  line-height: 1.1;
}
.calc-note { font-size: var(--text-label); color: var(--text-dim); margin-top: 4px; }

.calc-fields { display: flex; flex-direction: column; gap: 10px; }
.calc-field { display: flex; flex-direction: column; gap: 3px; }
.calc-label {
  font-size: var(--text-label); color: var(--text-dim); letter-spacing: .5px;
}
.calc-field input {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 12px;
  color: var(--text);
  font-family: var(--font);
  /* The right size for a number being thumbed in, and nothing to do with iOS
     zoom: this page declares itself non-scalable, so a field's size cannot
     trigger one. */
  font-size: var(--text-title);
  width: 100%;
  /* The spinners are a mis-tap that changes a loan amount, and they eat the
     room the digits need. */
  -moz-appearance: textfield;
}
.calc-field input::-webkit-outer-spin-button,
.calc-field input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.calc-field input:focus { outline: none; border-color: var(--accent-think); }
.calc-field input::placeholder { color: var(--border); }

.calc-clear {
  margin-top: 14px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text-dim);
  font-family: var(--font);
  font-size: var(--text-small);
  padding: 9px 14px;
}

/* The optional price/down row above the loan amount. Two across, because
   they are read together and a phone has the width for it. */
.calc-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }

/* The down payment carries its own unit button, inside the field's frame so
   the pair reads as one control. */
.calc-with-unit { display: flex; gap: 6px; }
.calc-with-unit input { flex: 1 1 auto; min-width: 0; }
.calc-unit {
  flex: 0 0 auto;
  width: 44px;                 /* a thumb target, not a hit-and-hope */
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--accent-brand);
  font-family: var(--font);
  font-size: var(--text-title);
  font-weight: 600;
}
.calc-unit:active { background: var(--brand-mint); color: var(--brand-green); }

/* Derived from the price, and not yours to type in. Dimmed rather than
   disabled: still readable and still selectable to copy. */
.calc-field input.derived { color: var(--text-dim); border-style: dashed; }

/* ===================== CRM =====================
   The deals board and one deal's file. The board is a desktop surface — the
   owner's split is that a phone is for reference and logging on the move,
   a desk is for seeing the whole pipeline and acting on it (2026-10-07).

   So the same markup takes two shapes rather than one reflowed one: columns
   side by side at desk width, and a list grouped by stage on a phone. A board
   is not a list that got wider. */
#crmView { justify-content: flex-start; align-items: stretch; width: 100%; }
.crm-shell { display: flex; gap: 14px; width: 100%; min-height: 0; flex: 1 1 auto; }
.crm-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.crm-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 10px; flex: 0 0 auto;
}
.crm-head .chip { flex: 0 0 auto; padding: 5px 12px; }

/* Phone: one column per stage, stacked. */
.crm-board { display: flex; flex-direction: column; gap: 14px; overflow-y: auto; min-height: 0; }
.crm-col { min-width: 0; }
.crm-col-head {
  display: flex; align-items: baseline; gap: 8px;
  padding-bottom: 6px; border-bottom: 1px solid var(--border); margin-bottom: 8px;
}
.crm-col-name {
  font-size: var(--text-label); color: var(--text-dim);
  letter-spacing: .5px; text-transform: uppercase;
}
.crm-col-count { font-size: var(--text-label); color: var(--accent-brand); }
/* The border colour was a faint mark on deep green and is invisible on
   white — an empty column looked like a broken one. */
.crm-col-empty { font-size: var(--text-small); color: var(--text-dim); padding: 2px 0 6px; }

.crm-card {
  width: 100%; text-align: left;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: 10px;
  padding: 9px 11px; margin-bottom: 7px;
  font-family: var(--font); color: var(--text);
  display: flex; flex-direction: column; gap: 2px;
}
.crm-card.open { border-left-color: var(--accent); }
.crm-card-name { font-size: var(--text-body); font-weight: 600; }
.crm-card-what { font-size: var(--text-small); color: var(--text-dim); }
.crm-card-partner { font-size: var(--text-label); color: var(--accent-brand); }
.crm-card-foot { font-size: var(--text-label); color: var(--text-dim); margin-top: 3px; }

/* ===================== The four sections =====================
   Leads, Loans and Rehab, each folding open to its own columns (owner,
   2026-10-30). Six equal columns in a row answered the detail question and
   never the first one — am I selling, closing, or rescuing? */
.crm-section { min-width: 0; }
.crm-section-head {
  display: flex; align-items: baseline; gap: 8px;
  width: 100%; padding: 2px 0 10px;
  background: none; border: none;
  font-family: var(--font); color: var(--text); text-align: left;
}
.crm-section-name { font-size: var(--text-heading); font-weight: 600; }
.crm-section-count {
  font-size: var(--text-small); font-weight: 600; color: var(--accent-brand);
}
.crm-section-caret { font-size: var(--text-small); color: var(--text-dim); }

/* `display: none` written out, because the board is a flex container and a
   flex parent beats a bare `hidden` attribute — the same way a hidden view
   once kept its space on the Today screen. */
.crm-strip { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.crm-strip[hidden] { display: none; }

/* Where a funded loan went. Without it a deal leaves the board on the day it
   closes and nothing on the screen says where to. */
.crm-elsewhere {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; margin-top: 4px;
  padding: 10px 12px;
  background: var(--surface-alt);
  border: 1px solid var(--border); border-radius: 10px;
  font-family: var(--font); font-size: var(--text-body); color: var(--text);
  text-align: left;
}
.crm-elsewhere-count { font-weight: 600; color: var(--accent-brand); }

/* ===================== Closed Loans =====================
   A list rather than a board: its three statuses are an accounting tail, and
   what the owner comes here for is the client, the property and the loan. */
#closedView { justify-content: flex-start; align-items: stretch; width: 100%; }
.closed-list {
  display: flex; flex-direction: column; gap: 8px;
  overflow-y: auto; min-height: 0;
}
.closed-row {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; width: 100%; text-align: left;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  /* Green down the edge, where the board's cards are grey. These are the
     ones that worked. */
  border-left: 3px solid var(--accent-brand);
  border-radius: 10px;
  padding: 10px 12px;
  font-family: var(--font); color: var(--text);
}
.closed-main {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; gap: 2px;
}
.closed-name { font-size: var(--text-body); font-weight: 600; }
.closed-what { font-size: var(--text-small); color: var(--text-dim); }
.closed-side {
  /* Capped rather than left to size itself: "Broker Check Received" is wide
     enough at 13px to squeeze the address into three lines on a phone. It
     wraps to two short lines instead, and the left column keeps its room. */
  flex: 0 1 auto; max-width: 38%; text-align: right;
  display: flex; flex-direction: column; gap: 2px;
}
.closed-status {
  font-size: var(--text-small); font-weight: 600; color: var(--accent-brand);
}
/* One line: a date broken over two reads as two dates. */
.closed-when {
  font-size: var(--text-label); color: var(--text-dim); white-space: nowrap;
}

/* The deal file. On a phone it covers the board; at desk width it sits beside
   it, which is the point of the desktop view — act without losing the
   pipeline. */
.crm-detail {
  flex: 0 0 auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px;
  overflow-y: auto; min-height: 0;
}
.crm-detail[hidden] { display: none !important; }
/* Phone: the file *replaces* the board. There is no room to show both, and
   left beside it the panel pushed the page 200px wider than the screen. */
.crm-detail { width: 100%; min-width: 0; }
.crm-shell.deal-open .crm-main { display: none; }
/* ===================== The deal screen's type =====================
   One rule, applied everywhere on this screen (owner, 2026-10-15: "some
   things being unnecessarily big"):

     headline  title  (20) 600  — the client's name, once per screen
     detail    small  (13) 400  — what hangs off the headline
     caption   label  (11) 400  — REFERRAL PARTNER, STAGE, NOTES, CALL
     value     body   (15) 600  — the answer to a caption: the partner's
                                   name, the stage
     action    body   (15) 600  — the thing you came to do
     secondary small  (13)      — a control beside something more important

   What was wrong was not the sizes but the order. The referral partner's
   *name* was drawn at caption size — the smallest text on the screen, and
   smaller than the caption's own word — while the stage and the remove cross
   were the loudest things after the client. A name is content; a cross is
   not. */
.crm-detail-name { font-size: var(--text-title); font-weight: 600; }
.crm-detail-what { font-size: var(--text-small); color: var(--text-dim); margin-top: 2px; }

/* Shrink-to-fit, so the control is the width of the longest stage rather
   than the width of the screen (owner, 2026-10-15: "too big"). A status that
   spans the page reads as a button bar and competes with the one button that
   is actually the point of the screen.

   The floor is the longest stage name — "Pre-Approval Denied" — because
   without it the closed control is the width of whatever stage it happens to
   be on, and grows under your finger the moment you open it. */
/* The stage picker, now a section of the links container rather than a block
   of its own (owner, 2026-10-22), so it carries no outer margin. */
.crm-stages { display: inline-block; min-width: 170px; margin: 2px 0 0; }

/* The stage while you are only reading the file. Same step and weight as
   every other value in that container, so nothing in it shouts. */
.crm-stage-face {
  font-size: var(--text-small); font-weight: 700; color: var(--text);
  min-height: 24px; display: flex; align-items: center;
}
/* Where it is. Tapping offers the moves; the six were a control panel laid
   out flat on the screen you open most.

   In the page's own colours (owner, 2026-10-14). A mint pill made the stage
   look like something being flagged at you, when it is simply what the screen
   says. */
.crm-stage-now {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 12px;
  color: var(--text);
  /* A status, at detail size. It was at value size and full width, which made
     where a file *is* as loud as the thing you came to the screen to do. */
  font-family: var(--font); font-size: var(--text-small); font-weight: 600;
  text-align: left;
}
.crm-stage-caret { font-size: var(--text-small); color: var(--text-dim); }

/* A dropdown list, one stage a line (owner, 2026-10-14) — read down and
   tapped once, rather than hunted for in a wrapped row of pills. */
.crm-stage-menu {
  display: flex; flex-direction: column;
  margin-top: 6px;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}
.crm-stage-menu[hidden] { display: none; }
.crm-stage-menu .crm-stage {
  width: 100%;
  background: var(--bg);
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  /* Roomier than the closed control: these are targets, and the one above is
     a label you happen to be able to tap. */
  padding: 11px 12px;
  color: var(--text);
  font-family: var(--font); font-size: var(--text-small);
  text-align: left;
}
.crm-stage-menu .crm-stage:last-child { border-bottom: none; }
.crm-stage-menu .crm-stage:active { background: var(--surface-alt); }
.crm-stages-label {
  font-size: var(--text-label); color: var(--text-dim);
  letter-spacing: .5px; margin-bottom: 5px;
}
.crm-stage-row { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.crm-stage {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-dim);
  font-family: var(--font); font-size: var(--text-label);
  padding: 5px 10px;
}
.crm-stage.on {
  background: var(--brand-mint); border-color: var(--accent-brand);
  color: var(--brand-green); font-weight: 600;
}
.crm-stage:disabled { opacity: 0.5; }

/* ===================== Desk: the left menu =====================
   Hidden on a phone, which navigates by thumb from the home screen. Styled
   out here rather than inside the media query so the hidden state is not
   conditional on it. */
.desk-nav { display: none; }
.desk-link {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  background: none; border: none; border-radius: 10px;
  padding: 10px 12px;
  color: var(--text);
  font-family: var(--font); font-size: var(--text-body);
  text-align: left;
}
.desk-link svg { width: 20px; height: 20px; flex: 0 0 auto; fill: var(--accent-brand); }
.desk-link:hover { background: var(--surface); }
/* The screen you are on. Filled rather than tinted: on a white ground a wash
   of green is easy to lose at a glance. */
.desk-link.on {
  background: var(--brand-green);
  color: #FFFFFF;
  font-weight: 600;
}
.desk-link.on svg { fill: var(--brand-mint); }

/* The desk home: the middle of the screen carries the day. */
.desk-home { display: none; }
.desk-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px 16px;
  display: flex; flex-direction: column; min-height: 0;
}
.desk-panel-title {
  margin: 0 0 10px;
  font-size: var(--text-title); font-weight: 600;
  color: var(--brand-green);
}
.desk-panel-body { overflow-y: auto; min-height: 0; }
.desk-empty { font-size: var(--text-small); color: var(--text-dim); }
/* A line in a panel: what it is on the left, when or how many on the right. */
.desk-row {
  display: flex; align-items: baseline; gap: 10px;
  width: 100%;
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
  background: none; border-left: none; border-right: none; border-top: none;
  color: var(--text); font-family: var(--font); font-size: var(--text-body);
  text-align: left;
}
.desk-row:last-child { border-bottom: none; }
button.desk-row:hover { background: var(--surface-alt); }
.desk-row-main { flex: 1 1 auto; min-width: 0; }
.desk-row-sub { display: block; font-size: var(--text-small); color: var(--text-dim); }
.desk-row-side { flex: 0 0 auto; font-size: var(--text-small); color: var(--text-dim); }
.desk-row-side.due { color: var(--accent); font-weight: 600; }
/* The pipeline, as a count a stage at a time. */
.desk-stage {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  width: 100%;
  padding: 7px 0; border: none; border-bottom: 1px solid var(--border);
  background: none; color: var(--text);
  font-family: var(--font); font-size: var(--text-body); text-align: left;
}
.desk-stage:last-child { border-bottom: none; }
.desk-stage:hover { background: var(--surface-alt); }
.desk-stage-count { font-size: var(--text-title); font-weight: 600; color: var(--accent-brand); }
.desk-stage-count.none { color: var(--text-dim); font-weight: 400; }

@media (min-width: 1024px) {
  /* The window becomes a desk: the banner across the top, the menu down the
     left, and whatever screen you are on filling the rest. */
  body {
    display: grid;
    grid-template-columns: 236px minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
  }
  #topBar { grid-column: 1 / -1; }
  .desk-nav {
    grid-column: 1; grid-row: 2;
    display: flex; flex-direction: column; gap: 2px;
    padding: 16px 12px;
    border-right: 1px solid var(--border);
    overflow-y: auto;
  }
  main { grid-column: 2; grid-row: 2; }
  /* The sign-in screen covers everything; it is not part of the desk. */
  #signInScreen { grid-column: 1 / -1; grid-row: 1 / -1; }

  /* Home stops being six keys under a thumb and becomes the day. */
  #homeView {
    justify-content: flex-start;
    /* The base rule centres its children for the phone's key grid. On a desk
       the panels fill the width, so they stretch instead — without this the
       greeting sits in the middle of the window with the panels under it. */
    align-items: stretch;
    padding: 20px 24px 24px;
    overflow-y: auto;
  }
  .home-grid { display: none; }
  .home-greeting { padding-top: 0; text-align: left; }
  /* On the desk the panels carry the day, so the quote is a quiet line above
     them rather than the middle of the screen. */
  .home-quote { margin: 8px 0 0; }
  .home-welcome { font-size: var(--text-title); color: var(--text-dim); font-weight: 400; }
  .desk-home {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px;
    width: 100%;
    margin-top: 14px;
    align-items: start;
  }
  .desk-panel { max-height: 340px; }

  /* Desk: a sheet pinned to the bottom of a wide monitor reads as a browser
     notification. Centred, with the scrim all round it, it reads as the same
     pattern the phone shows. */
  .sheet {
    margin: auto;
    width: min(560px, calc(100% - 48px));
    /* `fit-content`, not `auto`. A modal dialog gets `inset: 0` from the
       browser, and an absolutely positioned box with both edges pinned and
       `height: auto` stretches to fill the gap instead of hugging what is
       in it — so the sheet opened at its full cap whatever it contained. */
    height: fit-content;
    max-height: 85vh;
    border-radius: 16px;
    padding-bottom: 0;
  }
  @supports (max-height: 85dvh) {
    .sheet { max-height: 85dvh; }
  }
  /* The inner keeps the base `height: 100%`, which now resolves against a
     fit-content dialog: short content gives a short sheet, long content caps
     at 85% and the body scrolls inside it. Overriding it to `auto` here let
     the inner grow past the cap and get clipped by the sheet instead. */
  .sheet[open] { animation: sheet-fade 160ms ease; }
  @keyframes sheet-fade { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: none; } }
  .sheet-grab { display: none; }
  .sheet-head { padding-top: 14px; }


  /* Desk width: the board becomes a board. Columns sit side by side and scroll
     sideways if the pipeline outgrows the screen, rather than wrapping — a
     stage that moved to a second row would read as a different thing. */
  /* The sections stack down the page and each one scrolls its own columns
     sideways. Before them the whole board was a single long row, which is why
     this rule used to be the other way round. */
  .crm-board { overflow-x: hidden; overflow-y: auto; }
  .crm-strip {
    flex-direction: row; align-items: flex-start;
    gap: 12px; overflow-x: auto; padding-bottom: 4px;
  }
  .crm-col {
    flex: 1 0 210px; max-width: 280px;
    display: flex; flex-direction: column; min-height: 0;
  }
  .crm-detail { flex: 0 0 380px; max-width: 380px; width: auto; }
  /* Desk: both at once, which is the whole point of the desktop view — work
     a deal without losing sight of the pipeline. */
  .crm-shell.deal-open .crm-main { display: flex; }
  main { padding: 20px 24px; }
}

/* Where a deal entry came from, when it came out of somebody else's
   conversation. Mint, like every other "this is a partner" cue. */
.crm-heard {
  font-size: var(--text-label);
  color: var(--accent-brand);
  letter-spacing: .4px;
  margin-top: 2px;
}

/* ===================== Recording into something =====================
   Where a dictation is going, when it was started from a deal or a partner.
   Gold, because it is the one thing on the Talk screen that changes what
   happens to the words. */
.talk-context {
  display: flex; align-items: center; gap: 8px;
  width: 100%; max-width: 480px; flex: 0 0 auto;
  background: var(--surface);
  border: 1px solid var(--accent);
  border-radius: 999px;
  padding: 6px 8px 6px 14px;
  font-size: var(--text-small);
  color: var(--accent);
}
.talk-context[hidden] { display: none !important; }
.talk-context span { flex: 1 1 auto; min-width: 0; }
.talk-context button {
  flex: 0 0 auto;
  width: 26px; height: 26px;
  background: none; border: none; border-radius: 50%;
  color: var(--accent);
  font-family: var(--font); font-size: var(--text-title);
  line-height: 1;
}

/* The button that starts one, on a deal or a contact. The action you came
   for, so it carries value weight — it was quieter than the stage beside it,
   which is a status. */
.log-here {
  width: 100%;
  margin-top: 12px;
  background: none;
  border: 1px solid var(--accent);
  border-radius: 10px;
  color: var(--accent);
  font-family: var(--font);
  font-size: var(--text-body); font-weight: 600;
  padding: 11px 12px;
}


/* Call, text, email — one tap each, drawn rather than written out (owner,
   2026-10-14). The number and the address were taking a band of the screen to
   say what three icons say.

   Sized as targets first: tapped one-handed, walking, is how these get used.

   A missing detail greys its button rather than removing it. Three buttons
   always in the same three places can be hit without looking; a row that
   reshuffles depending on what is on file cannot. */
.crm-reach { display: flex; gap: 8px; margin-top: 12px; }
.crm-reach-link {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  background: var(--surface-alt);
  border: 1px solid var(--accent-brand);
  border-radius: 10px;
  padding: 10px 6px;
  color: var(--accent-brand);
  text-decoration: none;
}
/* Email is a button (it opens the sheet) while Call and Text are anchors
   (the phone takes those over). The three must look identical, so the
   button's own defaults — font, cursor, the UA border — are cleared here. */
button.crm-reach-link { font: inherit; cursor: pointer; }
.crm-reach-link svg { width: 22px; height: 22px; fill: currentColor; }
.crm-reach-link:active { background: var(--surface); }
.crm-reach-label { font-size: var(--text-label); letter-spacing: .4px; }
/* Nothing on file to reach them by. Said rather than hidden, and plainly not
   tappable. */
.crm-reach-link.is-off {
  background: none;
  border-color: var(--border);
  color: var(--text-dim);
  opacity: 0.55;
}

/* A box on the deal file — who sent them. A box rather than a sentence,
   because it is a thing you reach for. */
.crm-box {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 9px 11px;
  margin-top: 10px;
}
.crm-box-label {
  font-size: var(--text-label); color: var(--text-dim);
  letter-spacing: .5px; margin-bottom: 3px;
}
.crm-box-name { font-size: var(--text-body); font-weight: 600; }
.crm-box-detail { font-size: var(--text-small); color: var(--text-dim); margin-top: 2px; }
/* The partner's name: a value, at value size. It was drawn at caption size,
   which made the person smaller than the word "REFERRAL PARTNER" above them. */
.crm-partner-link {
  display: inline-block; margin-top: 6px;
  background: none; border: 1px solid var(--accent-brand); border-radius: 8px;
  color: var(--accent-brand); text-decoration: none;
  font-family: var(--font); font-size: var(--text-body); font-weight: 600;
  padding: 6px 12px;
}
/* The partner on a deal: their name on the left, a red cross on the right
   that takes them off it (owner, 2026-10-14). */
.crm-partner-x {
  flex: 0 0 auto;
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  /* White, not the card's tint: the red measures 4.4:1 on the tinted ground
     and 5.3:1 on white, and this is the one control on the screen that
     destroys something. */
  background: var(--bg);
  border: 1px solid var(--accent-live);
  border-radius: 8px;
  color: var(--accent-live);
  /* Secondary: it sits beside a name and must not outweigh it. The target
     stays 34px — the glyph got smaller, not the thing you tap. */
  font-family: var(--font); font-size: var(--text-small); font-weight: 600;
  line-height: 1;
}
.crm-partner-x:active { background: rgba(192, 68, 42, 0.1); }
.crm-partner-x:disabled { opacity: 0.5; }

/* Setting who referred the deal, when nobody has. Quiet: it is an absence
   being offered, not something demanding attention. */
.crm-partner-set {
  display: inline-block; margin-top: 8px;
  background: none; border: 1px dashed var(--accent-brand); border-radius: 8px;
  color: var(--accent-brand);
  font-family: var(--font); font-size: var(--text-small);
  padding: 6px 10px;
}
.crm-partner-set[hidden] { display: none; }
/* --- The guideline library ---------------------------------------------
   Two controls that would otherwise render at the browser's defaults: a file
   input and a button. Both were caught at 24px and 13.3px by the type-scale
   walk — the third and fourth elements this project has lost that way, which
   is why that check exists. */

.guideline-file {
  display: block; width: 100%;
  margin-top: 8px;
  font-family: var(--font); font-size: var(--text-small); color: var(--text);
}
/* The button that spends money — indexing is charged per document — so it
   reads as an action rather than as a default control. */
.guideline-add {
  display: inline-block; margin-top: 10px;
  min-height: 36px;
  background: var(--accent); border: none; border-radius: 8px;
  color: var(--on-accent);
  font-family: var(--font); font-size: var(--text-small); font-weight: 600;
  padding: 8px 16px;
}
.guideline-add:disabled { opacity: 0.5; }

/* --- Everything a deal is tied to, in one container --------------------
   (owner, 2026-10-21) Client, Referral Partner and ARIVE together, with a
   pencil at the top right. Two modes: normally each person's row carries
   call/text/email on the right, and nothing can be detached; under the pencil
   the reach icons give up that place to the red crosses and a + appears.

   The crosses used to sit permanently beside names that get tapped all day,
   one slip from unpicking the file. */

.crm-links {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 9px 11px;
  margin-top: 10px;
}
/* The pencil sits in the flow, right-aligned, above the sections.
   It was absolutely positioned at first and overlapped the first person's
   email icon — the one row whose controls reach the top of the container.
   Nothing is gained by floating it that is worth a control landing on
   another control. */
/* A contact's facts card is listed on each of these rather than given rules
   of its own (owner, 2026-11-05: the pencil there should be the one here).
   One declaration, two cards: a change to the pencil cannot reach one and
   miss the other. */
.crm-links-head,
.contact-facts-head { display: flex; justify-content: flex-end; margin-bottom: 2px; }
.crm-links-edit,
.contact-facts-edit {
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 1px solid var(--border); border-radius: 8px;
  color: var(--text-dim);
  padding: 0;
}
.crm-links-edit svg,
.contact-facts-edit svg { width: 16px; height: 16px; fill: currentColor; }
.contact-facts-edit:active { background: var(--bg); }
/* On: the brand green, so the mode is visible rather than remembered. */
.crm-links-edit.on {
  border-color: var(--accent-brand);
  color: var(--accent-brand);
  background: var(--bg);
}

/* The three sections, separated by a rule: one group, not three cards
   nested inside a fourth. */
.crm-links-section + .crm-links-section {
  border-top: 1px solid var(--border);
  margin-top: 9px; padding-top: 9px;
}


/* One person: name on the left, and on the right whichever the mode calls
   for. One slot, two jobs, so the row never grows a second line. */
.crm-person {
  display: flex; align-items: center; gap: 8px;
  min-height: 36px;
}
.crm-person + .crm-person { margin-top: 4px; }
/* A name on a row.
   **One line, always.** A long one truncates with an ellipsis rather than
   wrapping: a wrapped name pushed the call/text/email buttons onto a second
   line and broke the column they share with every other row (owner,
   2026-10-21). `min-width: 0` is what lets a flex item shrink below its
   content at all — without it the text wins and the row grows. */
.crm-person-name {
  flex: 1 1 auto; min-width: 0;
  display: block;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* The `small` step, not `body` (owner, 2026-10-21: “match the body font, I
     think 13px” — 13px is `--text-small`; `--text-body` is 15). Bold to carry
     the drop in size, and the smaller face buys a few more characters before
     the name truncates. */
  font-size: var(--text-small); font-weight: 700; color: var(--text);
  text-align: left;
}
/* When it goes somewhere, it looks like it does — a button, not underlined
   text (owner's preference, 2026-10-21). Sized to the row rather than to its
   own text, which at 23px high was a smaller target than the icons beside it. */
button.crm-person-name {
  min-height: 36px; line-height: 22px;
  /* Tight horizontally on purpose: every pixel here is a pixel of name. */
  padding: 6px 8px;
  background: none; border: 1px solid var(--accent-brand); border-radius: 8px;
  color: var(--accent-brand); text-decoration: none;
  font-family: var(--font);
}
button.crm-person-name:active { background: var(--bg); }
/* Says why this one has no cross, rather than leaving a silent gap. */
.crm-person-note {
  flex: 0 0 auto;
  font-size: var(--text-label); color: var(--text-dim);
  letter-spacing: .4px;
}

/* Adding another client. Only under the pencil, and a + rather than a
   sentence: by then the section above it has already said what it is. */
.crm-add {
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  margin-top: 6px;
  background: none; border: 1px dashed var(--accent-brand); border-radius: 8px;
  color: var(--accent-brand);
  font-family: var(--font); font-size: var(--text-body); line-height: 1;
  padding: 0;
}
.crm-add[hidden] { display: none; }

/* The compact reach strip on a person's row. The same three links as the old
   full-width row, without their words: three labelled buttons per borrower
   would crowd the container, and it is the same three in the same order
   every time. */
.crm-reach-compact { flex: 0 0 auto; gap: 5px; margin-top: 0; }
.crm-reach-compact .crm-reach-link {
  flex: 0 0 auto;
  /* Narrowed from 42px to give the name more room before it truncates
     (owner, 2026-10-21). Still level with the cross that takes their place in
     edit mode, and still a thumb-sized target: these get tapped mid-call,
     which is the whole reason they are on the person's own row. */
  width: 34px; height: 36px;
  padding: 0;
  display: flex; align-items: center; justify-content: center;
}
.crm-reach-compact .crm-reach-link svg { width: 17px; height: 17px; }

/* --- The ARIVE loan on a deal ------------------------------------------
   Shaped after the referral partner box above, because it is the same kind
   of thing: a fact about the file with one control on each end. Every size
   comes off the scale — nothing here is a bare pixel value. */

/* Where the loan is, in ARIVE's words. At value size, like the partner's
   name: past pre-approval this is the truest thing on the screen, and it
   must not read smaller than the word "ARIVE" above it. */
.crm-arive-status {
  flex: 1 1 auto; min-width: 0;
  /* The same step and weight as the names above it (owner, 2026-10-21). It
     was left at body size when they dropped to 13px, which made the one line
     nobody asked about the largest thing in the container. */
  font-size: var(--text-small); font-weight: 700;
  color: var(--text);
}
/* The number, which is also the way into ARIVE. Bordered like the partner's
   name so a tappable thing looks tappable. */
.crm-arive-open {
  flex: 0 0 auto;
  display: flex; align-items: center;
  min-height: 34px;
  background: none; border: 1px solid var(--accent-brand); border-radius: 8px;
  color: var(--accent-brand); text-decoration: none;
  font-family: var(--font); font-size: var(--text-small); font-weight: 600;
  padding: 5px 9px;
}
/* No deep link yet, so the number is a label rather than a door. Greyed the
   same way a missing phone is on the reach row. */
.crm-arive-open.is-off {
  border-color: var(--border); color: var(--text-dim); opacity: 0.55;
}
.crm-arive-when {
  font-size: var(--text-label); color: var(--text-dim); margin-top: 4px;
}

/* The question. Not a heading — it is asking, and the candidates below are
   the answers. */
.crm-arive-ask {
  font-size: var(--text-small); color: var(--text); margin: 8px 0 6px;
}

/* One candidate loan. Tall enough to tap on a phone without aiming, and
   left-aligned because it is three lines of fact, not a label. */
.crm-arive-cand {
  display: block; width: 100%; text-align: left;
  background: var(--bg); border: 1px solid var(--border); border-radius: 10px;
  padding: 9px 11px; margin-bottom: 6px;
  color: var(--text); font-family: var(--font);
}
.crm-arive-cand:active { background: var(--surface-alt); }
.crm-arive-cand-top { font-size: var(--text-body); font-weight: 600; }
/* The property, which is what tells one cloned loan from another. */
.crm-arive-cand-sub {
  font-size: var(--text-small); color: var(--text-dim); margin-top: 2px;
}
/* Why it thinks so, so the card does not ask to be trusted. */
.crm-arive-cand-why {
  font-size: var(--text-label); color: var(--text-dim); margin-top: 3px;
}

/* Saying no. Quiet, like the other absence-offering buttons, because
   declining is a normal answer rather than a failure. */
.crm-arive-none {
  display: inline-block; margin-top: 2px;
  background: none; border: none;
  color: var(--text-dim); text-decoration: underline;
  font-family: var(--font); font-size: var(--text-small);
  padding: 4px 0;
}

/* Pasting the number by hand: the rare path, so it is plain. */
.crm-arive-manual { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.crm-arive-manual .pick-hint { flex: 1 1 100%; }
.crm-arive-input {
  flex: 1 1 auto; min-width: 0;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  padding: 7px 10px;
  color: var(--text); font-family: var(--font); font-size: var(--text-body);
}
.crm-arive-go {
  flex: 0 0 auto;
  background: var(--accent); border: none; border-radius: 8px;
  color: var(--on-accent);
  font-family: var(--font); font-size: var(--text-small); font-weight: 600;
  padding: 7px 14px;
}
.crm-arive-go:disabled { opacity: 0.5; }

/* The picker sits inside the box, so it keeps the box's width rather than
   breaking out of it. */
.crm-box .pick-panel { gap: 8px; margin-top: 8px; }

/* What a referral partner has sent, on their own file. A row reads as the
   deal it opens: who, what, and where it has got to. */
.partner-deal {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 10px 12px; margin-bottom: 6px;
  color: var(--text); font-family: var(--font); text-align: left;
}
.partner-deal-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.partner-deal-name { font-size: var(--text-body); font-weight: 600; }
.partner-deal-what {
  font-size: var(--text-small); color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.partner-deal-stage {
  flex: 0 0 auto;
  font-size: var(--text-label); letter-spacing: .3px;
  color: var(--accent-brand);
}

/* The four standing sections: what is known about the file, as it stands.
   Rewritten whenever a conversation is submitted onto it, so there is no
   button and nothing to be out of date. */

/* ---------------------------------------------------------------------------
   The plan on a deal file — the Action Plan in Rehab, the Refinance Plan on a
   closed loan (owner, 2026-11-02).

   It is the one authored thing on a file otherwise made of summaries, so it
   is given an edge in the brand green and a tinted ground: what you wrote is
   visibly different from what the app worked out about you.

   Every size comes from the six-step scale. Nothing here is a bare pixel.
   --------------------------------------------------------------------------- */
.plan { margin-top: 18px; }

.plan-blurb {
  font-size: var(--text-small);
  color: var(--text-dim);
  margin: -4px 0 10px;
}

.plan-body {
  border-left: 3px solid var(--accent-brand);
  padding-left: 12px;
}

.plan-part { margin-bottom: 18px; }
.plan-part:last-child { margin-bottom: 0; }

.plan-part-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 8px;
}

.plan-part-name {
  font-size: var(--text-small);
  font-weight: 600;
  color: var(--text);
}

.plan-add {
  background: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 11px;
  color: var(--accent-brand);
  font-family: var(--font);
  font-size: var(--text-label);
  font-weight: 600;
  letter-spacing: .3px;
  cursor: pointer;
  /* A tap target that a thumb finds without magnifying the chip itself. */
  min-height: 30px;
}
.plan-add:active { background: var(--surface-alt); }

/* ------------------------------------------------------------ issues */

.plan-issue {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 10px 12px;
  margin-bottom: 8px;
}

/* A problem that has been sorted stays on the file and stops shouting. It is
   the most useful line on the plan next time this borrower comes round, so it
   is dimmed rather than struck through or hidden. */
.plan-issue.is-sorted { opacity: .62; }

.plan-issue-tools {
  position: absolute;
  top: 6px;
  right: 8px;
  display: flex;
  gap: 2px;
}

.plan-tick,
.plan-drop {
  background: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  width: 26px;
  height: 26px;
  line-height: 1;
  padding: 0;
  color: var(--text-dim);
  font-family: var(--font);
  font-size: var(--text-small);
  cursor: pointer;
}
.plan-tick[aria-pressed="true"] {
  border-color: var(--accent-brand);
  color: var(--accent-brand);
  font-weight: 700;
}

.plan-field { display: block; margin-bottom: 6px; }
.plan-field:last-child { margin-bottom: 0; }

.plan-field-label {
  display: block;
  font-size: var(--text-label);
  color: var(--text-dim);
  letter-spacing: .5px;
  margin-bottom: 2px;
}

/* The write-up itself. No border and no box: it reads as text on the file
   rather than as a form, which is what a working document should feel like.
   The focus ring is the only thing that says it can be typed in. */
.plan-issue-text,
.plan-issue-fix,
.plan-fu-text {
  display: block;
  width: 100%;
  box-sizing: border-box;
  background: none;
  border: none;
  border-radius: 8px;
  padding: 3px 4px;
  margin: 0;
  color: var(--text);
  font-family: var(--font);
  font-size: var(--text-small);
  line-height: 1.45;
  resize: none;
  overflow: hidden;
}
.plan-issue-text { font-weight: 600; }

.plan-issue-text:focus,
.plan-issue-fix:focus,
.plan-fu-text:focus {
  outline: none;
  background: var(--surface-alt);
}

.plan-issue-text::placeholder,
.plan-issue-fix::placeholder,
.plan-fu-text::placeholder { color: var(--text-dim); opacity: .7; }

/* The crosses sit top-right of an issue, so the first line must not run
   under them. The follow-up rows carry their own cross on the date row and
   need no such allowance. */
.plan-issue .plan-field:first-of-type .plan-issue-text { padding-right: 56px; }

/* ----------------------------------------------------------- figures */

.plan-figures {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: 12px;
}

/* The home value is the one he is guessing at, and the one he will come back
   to. A row of its own rather than squeezed beside the term. */
.plan-figure:last-child { grid-column: 1 / -1; }

/* `min-width: 0` on the grid item as well as the input. A date input carries
   an intrinsic minimum width, and a grid item defaults to `min-width: auto`,
   so without this the date's column grew to 172px against the other's 110px
   and the two-column grid came out visibly lopsided. Nothing was clipped —
   it just looked careless. */
.plan-figure {
  display: flex; flex-direction: column; gap: 3px;
  min-width: 0;
}

.plan-figure-input {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 9px 10px;
  color: var(--text);
  font-family: var(--font);
  font-size: var(--text-body);
  width: 100%;
  box-sizing: border-box;
  /* Safari gives a date input its own intrinsic width and centres the text;
     both make a two-column grid ragged. */
  min-width: 0;
  text-align: left;
}
.plan-figure-input:focus { outline: none; border-color: var(--accent-brand); }
.plan-figure-input::placeholder { color: var(--border); }
.plan-figure-input::-webkit-date-and-time-value { text-align: left; }

.plan-reading {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 4px 12px;
}

.plan-line { padding: 9px 0; border-bottom: 1px solid var(--border); }
.plan-line:last-child { border-bottom: none; }

.plan-line-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.plan-line-label { font-size: var(--text-small); color: var(--text-dim); }

.plan-line-value {
  font-size: var(--text-heading);
  font-weight: 700;
  white-space: nowrap;
}

/* The caveat under every figure — what it assumes, and that a survey average
   is not a quote. It is small, and it is never optional. */
.plan-line-note {
  margin-top: 3px;
  font-size: var(--text-label);
  color: var(--text-dim);
  line-height: 1.5;
}

/* -------------------------------------------------------- follow-ups */

.plan-fu {
  border-top: 1px solid var(--border);
  padding: 9px 0;
}
.plan-fu:first-of-type { border-top: none; }

.plan-fu-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.plan-fu-date {
  background: none;
  border: none;
  padding: 0;
  color: var(--accent-brand);
  font-family: var(--font);
  font-size: var(--text-small);
  font-weight: 600;
  min-width: 0;
}
.plan-fu-date:focus { outline: none; text-decoration: underline; }

.plan-fu-logged {
  margin-top: 2px;
  font-size: var(--text-label);
  color: var(--text-dim);
}

/* **No desk override, and that is the fix rather than the omission.** The
   first version widened the grid to three columns at `min-width: 1024px`, on
   the assumption that a wide window means a wide panel. It does not: at a desk
   the deal file is a column beside the board, so the panel measured 335px at
   1280 against 375 on a phone. The override squeezed five figures into 101px
   each — a date field and "Home value — your estimate" in an inch — to make
   use of room the panel never had.

   The breakpoint would have to be on the panel rather than the viewport, and
   two columns already fit both. So one rule serves both widths. */

.deal-sections { margin-top: 14px; }
.deal-line { padding: 6px 0; border-bottom: 1px solid var(--border); }
.deal-line:last-child { border-bottom: none; }
.deal-line-text { font-size: var(--text-small); }
.deal-line-source {
  margin-top: 2px;
  font-size: var(--text-label); color: var(--text-dim);
}

/* ===================== Tasks on a deal =====================
   Built 2026-11-04, replacing the "Next steps and timeline" heading that the
   model used to write. Tinted on purpose: every other block on this file
   records what has happened, and this is the one that says what has not. The
   left edge carries the brand green so it reads as deliberate rather than as
   a box that came out a different colour. */
.deal-tasks {
  background: var(--surface-mint);
  border: 1px solid var(--brand-mint);
  border-left: 3px solid var(--accent-brand);
  border-radius: 10px;
  padding: 9px 11px;
  margin-top: 12px;
}
.deal-tasks-label {
  font-size: var(--text-label); font-weight: 600; color: var(--accent-brand);
  letter-spacing: .5px; margin-bottom: 4px;
}
.deal-tasks-wait,
.deal-tasks-clear { font-size: var(--text-small); color: var(--text-dim); }
.deal-tasks-problem {
  font-size: var(--text-small); color: var(--accent); margin-top: 6px;
}
.deal-tasks-caveat {
  font-size: var(--text-label); color: var(--text-dim); margin-top: 6px;
}

/* A row: the tick, then what it is and when. `align-items: flex-start` and
   not `baseline` — a title that wraps to two lines would drag the tick down
   the row with it. */
.deal-task {
  display: flex; gap: 8px; align-items: flex-start;
  padding: 7px 0; border-top: 1px solid var(--brand-mint);
}
.deal-task:first-of-type { border-top: none; }
/* A subtask, indented by exactly the tick's width plus its gap, so its title
   lines up under its parent's rather than near it. */
.deal-task.is-child { padding-left: 30px; }
.deal-task-main { flex: 1 1 auto; min-width: 0; }
.deal-task-title {
  font-size: var(--text-body); line-height: 1.35; overflow-wrap: anywhere;
}
.deal-task-when {
  font-size: var(--text-label); color: var(--text-dim); margin-top: 2px;
}
/* Overdue is the only thing on this card allowed to be loud. */
.deal-task-when.is-late { color: var(--accent); font-weight: 600; }

/* The communication history: when, and how. "Notes" opens onto the words as
   they were dictated. */
.history-row .history-notes {
  flex: 0 0 auto;
  /* A thumb target, not just a word: 25px tall was comfortably missable at
     arm's length, and this sits in a list you scan down. */
  min-height: 34px;
  display: inline-flex; align-items: center;
  background: none; border: 1px solid var(--border); border-radius: 8px;
  color: var(--text-dim);
  font-family: var(--font); font-size: var(--text-label);
  padding: 3px 12px;
}
.history-row .history-notes.open {
  border-color: var(--accent-brand); color: var(--accent-brand);
}
.history-said {
  padding: 2px 0 8px;
  border-bottom: 1px solid var(--border);
}
.history-said[hidden] { display: none; }
.history-said-text {
  margin: 4px 0 0;
  font-size: var(--text-small); color: var(--text-dim);
  font-style: italic;
  white-space: pre-wrap;
}

.crm-profile { margin-top: 14px; }

/* Opening a file: the + on the board, and the two questions it asks. */
.crm-new {
  flex: 0 0 auto;
  width: 32px; height: 32px;
  border-radius: 50%;
  background: var(--brand-green); border: none;
  color: var(--brand-mint);
  font-family: var(--font); font-size: var(--text-title); font-weight: 600;
  line-height: 1;
}
.crm-new:active { transform: scale(0.94); }
/* Over the board while it is open: one question at a time, nothing else to
   look at. */
.crm-new-flow { margin-bottom: 12px; }
.crm-new-flow[hidden] { display: none; }

/* The name leads the screen now, with the deal under it (owner, 2026-10-13):
   who it is, how to reach them, where it has got to, who sent it, what has
   been said. */
#crmDetailBody > .crm-detail-name { margin-top: 4px; }

/* Out of the deal: an arrow in the top right (owner, 2026-10-14), with the
   phone's own swipe doing the same thing. Floated out of the flow so the
   client's name still starts at the top of the screen rather than below a
   band holding one button. */
.crm-detail { position: relative; }
.crm-back {
  position: absolute; top: 10px; right: 12px; z-index: 2;
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; padding: 0;
  color: var(--accent-brand);
}
.crm-back svg { width: 24px; height: 24px; fill: currentColor; }
.crm-back:active { opacity: 0.6; }
/* Room for it, so a long client name does not run under the arrow. */
#crmDetailBody > .crm-detail-name { padding-right: 44px; }

/* Submit: the tap that makes a memo real. Gold, because until it is pressed
   nothing on the card exists anywhere. */
.submit-row { display: flex; gap: 8px; margin-top: 14px; }
.submit-note {
  flex: 1 1 auto;
  background: var(--accent); border: 1px solid var(--accent); color: var(--on-accent);
  border-radius: 10px; font-family: var(--font); font-size: var(--text-body);
  font-weight: 600; padding: 11px 14px;
}
.submit-note:disabled { opacity: 0.6; }
.discard-note {
  flex: 0 0 auto;
  background: none; border: 1px solid var(--border); color: var(--text-dim);
  border-radius: 10px; font-family: var(--font); font-size: var(--text-small);
  padding: 11px 14px;
}
.discard-note:disabled { opacity: 0.5; }

/* Drop one proposed task without taking the rest. Quiet: it sits beside a
   line you are reading, not competing with Submit. */
.step-drop {
  background: none; border: none; color: var(--text-dim);
  font-family: var(--font); font-size: var(--text-label);
  text-decoration: underline; padding: 2px 0; margin-top: 2px;
  align-self: flex-start;
}
.step-drop:disabled { opacity: 0.5; }

/* ===================== User settings =====================
   Reached from the profile menu rather than the home grid (owner,
   2026-10-29): setup you visit occasionally, not a destination. */
.settings-note {
  font-size: var(--text-small);
  color: var(--text-dim);
  margin: 0 0 12px;
  line-height: 1.45;
}
/* One line for both "that is not a web address" and "Saved." The screen has
   one thing to say at a time, and two separate slots would leave a stale
   success sitting under a fresh error. */
.settings-problem {
  font-size: var(--text-small);
  color: var(--accent);
  margin-top: 8px;
}
.settings-current {
  font-size: var(--text-small);
  color: var(--text-dim);
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  overflow-wrap: anywhere;
}
.settings-current a { color: var(--accent-brand); }

/* ===================== Loan calendar =====================

   A month grid, chosen over a list of what is due (owner, 2026-11-03).

   **The grid is fixed and the strip below it scrolls.** The shell is exactly
   as tall as the viewport and does not scroll, so something here has to be
   the part that gives — and it cannot be the grid, because a month that
   scrolls is not a month you can see. Six week rows always, so the squares
   never resize when the month changes under the reader's finger.

   Squares carry dots, not words: at phone width a square is about fifty
   pixels across and "CD acknowledged by" has never fitted in one. The words
   are in the strip. */
#calendarView { justify-content: flex-start; align-items: stretch; width: 100%; }

.cal-head {
  display: flex; align-items: center; gap: 8px;
  flex: 0 0 auto;
}
.cal-month {
  /* Not stretched: letting the month name take the slack put the two
     chevrons at opposite ends of a desk-width header, where they stop
     reading as a pair. They stay beside the name and Today keeps the right
     edge to itself. */
  flex: 0 0 auto;
  font-size: var(--text-heading);
  font-weight: 600;
  min-width: 140px;
}
.cal-today { margin-left: auto; }
.cal-step, .cal-today {
  flex: 0 0 auto;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
  font-family: var(--font);
  /* 36px rather than the text's own height: these are the two controls that
     get pressed repeatedly, and a chevron sized to its glyph is a miss. */
  min-width: 36px; min-height: 36px;
  cursor: pointer;
}
.cal-step { font-size: var(--text-title); line-height: 1; padding: 0 4px; }
.cal-today { font-size: var(--text-small); padding: 0 12px; }

.cal-grid { flex: 0 0 auto; display: flex; flex-direction: column; gap: 2px; }
.cal-dow, .cal-week { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.cal-dow-cell {
  text-align: center;
  font-size: var(--text-label); font-weight: 600; letter-spacing: 0.4px;
  color: var(--text-dim);
  padding-bottom: 2px;
}
/* The column the office never works, dimmed rather than explained. */
.cal-dow-cell.off { opacity: 0.45; }

.cal-day {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  gap: 2px;
  /* Tall enough to press, and six of them plus the weekday row has to fit
     above the strip on a phone. */
  min-height: 42px;
  padding: 3px 1px 2px;
  background: var(--bg);
  border: 1px solid var(--border); border-radius: 8px;
  font-family: var(--font);
  cursor: pointer;
}
.cal-day-num { font-size: var(--text-small); line-height: 1.1; }
/* Outside the month on screen, but still a real day: a closing on the 1st of
   next month is worth seeing from this one. */
.cal-day.out { opacity: 0.4; }
/* Shut: a Sunday, a holiday, or the firm's own closure. The LE clock skips
   these, which is the only reason the browser is told about them. */
.cal-day.shut { background: var(--surface-alt); }
.cal-day.today { border-color: var(--accent-brand); border-width: 2px; }
.cal-day.picked { background: var(--surface); border-color: var(--accent-brand); border-width: 2px; }

.cal-day-holiday {
  position: absolute; top: 3px; right: 3px;
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--text-dim);
}

.cal-dots { display: flex; gap: 2px; flex-wrap: wrap; justify-content: center; }
.cal-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--text-dim); }

/* **Only a deadline is allowed to be loud.** The rest are facts ARIVE sent,
   and a record of what happened should not read like a thing still to do. */
.cal-dot.tone-deadline  { background: var(--accent); }
.cal-dot.tone-met       { background: var(--accent-brand); }
.cal-dot.tone-expiry    { background: var(--accent-live); }
.cal-dot.tone-milestone { background: var(--text); }
.cal-dot.tone-info      { background: var(--text-dim); }

.cal-strip-head { flex: 0 0 auto; margin-top: 4px; }
.cal-strip-head .panel-label { margin-bottom: 0; }
.cal-strip {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 6px;
}
.cal-empty, .cal-note {
  font-size: var(--text-small); color: var(--text-dim); margin: 2px 0;
}

.cal-row {
  display: flex; flex-direction: column; gap: 2px;
  width: 100%; text-align: left;
  background: var(--surface-alt);
  border: 1px solid var(--border); border-radius: 10px;
  padding: 8px 10px;
  font-family: var(--font); color: var(--text);
  cursor: pointer;
}
.cal-row.late { border-left: 3px solid var(--accent-live); }
.cal-row-top { display: flex; align-items: baseline; gap: 8px; }
.cal-row-tag {
  flex: 0 0 auto;
  font-size: var(--text-label); font-weight: 600; letter-spacing: 0.3px;
  color: var(--on-accent); background: var(--text-dim);
  border-radius: 6px; padding: 2px 6px;
}
.cal-row-tag.tone-deadline  { background: var(--accent); }
.cal-row-tag.tone-met       { background: var(--accent-brand); }
.cal-row-tag.tone-expiry    { background: var(--accent-live); }
.cal-row-tag.tone-milestone { background: var(--text); }
.cal-row-what {
  flex: 1 1 auto; min-width: 0;
  font-size: var(--text-small); font-weight: 600;
}
.cal-row-when { flex: 0 0 auto; font-size: var(--text-label); color: var(--text-dim); }
.cal-row-who { font-size: var(--text-small); color: var(--text-dim); }
/* Which section the date was counted from. The owner checks the app against
   the rule rather than taking its word, and this is what makes that possible
   without leaving the screen. */
.cal-row-why { font-size: var(--text-label); color: var(--text-dim); }

@media (min-width: 1024px) {
  /* At a desk there is room for the squares to breathe, and the strip sits
     beside the grid rather than under it. */
  #calendarView { display: grid; grid-template-columns: 1fr 320px;
    grid-template-rows: auto 1fr; gap: 16px 20px; align-content: start; }
  .cal-head { grid-column: 1 / -1; }
  .cal-grid { grid-column: 1; }
  .cal-day { min-height: 76px; }
  .cal-day-num { font-size: var(--text-body); }
  .cal-strip-head { grid-column: 2; grid-row: 2; align-self: start; }
  .cal-strip { grid-column: 2; grid-row: 2; margin-top: 22px; }
}
/* A section number is one word, whatever it looks like. Allowed to wrap and
   never to break: "1026.19(e)(1)" on one line and "(iii)(A)" on the next
   reads as two half-citations. */
.cal-cite { white-space: nowrap; }
