/*
  The app's styling. Deliberately the SAME visual language as the contact page the frame shows -
  same ink, same soft ground, same small-radius shapes - because the two are seen one inside the other and
  a different palette would read as two products glued together.

  ⭐ THE BUSINESS LOOK (owner, 2026-10-13: "looks like a less professional version of WhatsApp"; the picture is
  documentation/mockups/business-look.html). Wave A, the surface:
    - NO WHATSAPP COLOUR ANYWHERE: Send is the accent like every primary button; the conversation is drawn from
      the app's tokens (AppTokens.FrameVars / FrameFlat).
    - SQUARE CONTROLS: radii are 4 / 6 / 8px. `--radius-pill` is only for what is round by nature (a contact's
      disc, a status dot, a task's tick, a spinner, a progress track) - never a tab, chip, badge or button.
    - FLAT PANELS: no shadow on anything that is part of the page; shadows are for what floats over it (a menu,
      a dialog, the phone's AI sheet, a tooltip). On the three-pane screen the panes are divided by 1px rules -
      see the `wac-business` block at the end of this file.

  ⚠ BROWSED, NOT READ (owner ruling, 2026-09-12). Rows are scanned down the left edge: label, then
  value, then who it belongs to. Nothing is centred, nothing is justified, and no row wraps to a
  second line on a desktop width.

  ⭐ THE DESIGN SYSTEM (owner-approved 2026-09-30; the written guide is documentation/design/style-guide.html):
    - every colour, size, weight, space and radius is a TOKEN from App/src/AppTokens.ts, copied into the block
      below (a test keeps the copy exact). Old names (--ink, --dim, --bg, --soft, --acc, --ai, --live ...) are
      aliases of the new ones, so a rule may use either - a NEW rule uses the new names.
    - ONE button family, `.btn` (primary = accent fill) with `.secondary`, `.ghost`, `.danger`, `.send`, `.sm`,
      `.icon`, and one focus ring. `.send` is a hook for code and tests: it looks like the primary button.
    - headings are sentence case, semibold (600), never capitals with wide tracking.
    - ⛔ NO `prefers-color-scheme` BELOW THE TOKEN BLOCK: a rule that hard-codes a dark colour would ignore the
      owner's own theme choice (`data-theme`). Colours come from tokens; tokens change with the theme.
*/
/* TOKENS:BEGIN - generated by AppTokens.Css() in App/src/AppTokens.ts. Edit there, never here. */
:root {
    color-scheme: light;
    --text: #2b2f36;
    --muted: #5c6370;
    --page: #f6f7f9;
    --surface: #ffffff;
    --line: #e3e6ec;
    --line-strong: #858c99;
    --accent: #095495;
    --accent-hover: #07477e;
    --accent-soft: #eaf2f9;
    --accent-line: #c7dcef;
    --on-accent: #ffffff;
    --success: #4f8035;
    --success-soft: #eef5ea;
    --success-line: #cfe3c4;
    --success-deep: #3b6128;
    --warning: #a5561b;
    --warning-soft: #fdf3e3;
    --warning-line: #f0dfc0;
    --warning-ink: #7a4a05;
    --danger: #b42318;
    --danger-soft: #fdece9;
    --danger-line: #f3cfc8;
    --mark: #fbe7a1;
    --on-mark: #2b2f36;
    --mebubble: #eaf2f9;
    --disc: #dfe3ee;
    --disc-ink: #5b6474;
    --viz1: #2a78d6;
    --viz2: #eb6834;
    --viz3: #1baf7a;
    --viz4: #eda100;
    --viz5: #e87ba4;
    --viz6: #008300;
    --text-xs: 12px;
    --text-sm: 13px;
    --text-base: 14px;
    --text-md: 16px;
    --text-lg: 20px;
    --text-xl: 24px;
    --weight-regular: 400;
    --weight-strong: 600;
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
    --radius-sm: 4px;
    --radius-md: 6px;
    --radius-lg: 8px;
    --radius-pill: 999px;
    --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --font-brand: Verdana, "Segoe UI", Calibri, Arial, sans-serif;
    --font-mono: ui-monospace, "Cascadia Mono", SFMono-Regular, Menlo, Consolas, monospace;
    --focus-ring: 2px solid var(--accent);
    --ink: var(--text);
    --dim: var(--muted);
    --bg: var(--surface);
    --soft: var(--page);
    --quiet: var(--muted);
    --acc: var(--accent);
    --ai: var(--accent);
    --aiink: var(--accent);
    --aisoft: var(--accent-soft);
    --ailine: var(--accent-line);
    --live: var(--success);
    --owed: var(--warning);
    --brandsoft: var(--success-soft);
    --brandline: var(--success-line);
    --branddeep: var(--success-deep);
    --warnbg: var(--warning-soft);
    --warnink: var(--warning-ink);
    --warnline: var(--warning-line);
    --badbg: var(--danger-soft);
    --badink: var(--danger);
    --badline: var(--danger-line);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;
        --text: #e6e9ef;
        --muted: #9aa3b6;
        --page: #0f141c;
        --surface: #161b26;
        --line: #2a3140;
        --line-strong: #6b7385;
        --accent: #6ea8e0;
        --accent-hover: #8fbdea;
        --accent-soft: #15263a;
        --accent-line: #26466b;
        --on-accent: #0f141c;
        --success: #7fb267;
        --success-soft: #16261a;
        --success-line: #2c4a24;
        --success-deep: #a6d08f;
        --warning: #e49b5c;
        --warning-soft: #2e2415;
        --warning-line: #4a3a1d;
        --warning-ink: #f0d9a8;
        --danger: #f28b82;
        --danger-soft: #3a1e1b;
        --danger-line: #5a2b25;
        --mark: #5c4a14;
        --on-mark: #fff3cf;
        --mebubble: #15263a;
        --disc: #2b3242;
        --disc-ink: #9aa3b6;
        --viz1: #3987e5;
        --viz2: #d95926;
        --viz3: #199e70;
        --viz4: #c98500;
        --viz5: #d55181;
        --viz6: #008300;
    }
}
:root[data-theme="dark"] {
    color-scheme: dark;
    --text: #e6e9ef;
    --muted: #9aa3b6;
    --page: #0f141c;
    --surface: #161b26;
    --line: #2a3140;
    --line-strong: #6b7385;
    --accent: #6ea8e0;
    --accent-hover: #8fbdea;
    --accent-soft: #15263a;
    --accent-line: #26466b;
    --on-accent: #0f141c;
    --success: #7fb267;
    --success-soft: #16261a;
    --success-line: #2c4a24;
    --success-deep: #a6d08f;
    --warning: #e49b5c;
    --warning-soft: #2e2415;
    --warning-line: #4a3a1d;
    --warning-ink: #f0d9a8;
    --danger: #f28b82;
    --danger-soft: #3a1e1b;
    --danger-line: #5a2b25;
    --mark: #5c4a14;
    --on-mark: #fff3cf;
    --mebubble: #15263a;
    --disc: #2b3242;
    --disc-ink: #9aa3b6;
    --viz1: #3987e5;
    --viz2: #d95926;
    --viz3: #199e70;
    --viz4: #c98500;
    --viz5: #d55181;
    --viz6: #008300;
}
/* TOKENS:END */
* { box-sizing: border-box; }
body {
    margin: 0; background: var(--page); color: var(--text);
    font: var(--text-base)/1.55 var(--font-ui);
}
/* ⭐ ONE FOCUS RING FOR EVERYTHING THE KEYBOARD CAN REACH (audit 2026-10-01): 2px accent, 2px away. It was written per
   component, so a row link, a task's tick, a chart's bar, the message box and the menus' items had a colour change or
   nothing at all. A rule further down may move the ring (`outline-offset`) - it may not take it away.
   ⚠ What the PAGE gives the focus (`tabindex="-1"`: the center pane, a dialog's body) draws no ring: the ring says "the
   keyboard is here, press Enter", and nothing happens when Enter is pressed on a pane.
   ⭐ A HEADING THE PAGE MOVED THE FOCUS TO IS THE EXCEPTION (audit 2026-10-12; sign-up's `#wac-step`): somebody working
   by keyboard must SEE where the focus went after a step, or the next Tab starts from a place they cannot find. The
   ring shows only for `:focus-visible` (keyboard), so a tap or click that lands the focus there draws nothing. */
:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
[tabindex="-1"]:focus, [tabindex="-1"]:focus-visible { outline: none; }
h1[tabindex="-1"]:focus-visible, h2[tabindex="-1"]:focus-visible, h3[tabindex="-1"]:focus-visible {
    outline: var(--focus-ring); outline-offset: 4px; border-radius: var(--radius-sm);
}
/* Placeholders are hints, read like any hint: `--muted` (the browser's own grey was 3.7:1 on the dark theme's fields). */
::placeholder { color: var(--muted); opacity: 1; }
/* The top bar (business look wave B, 2026-10-13): a slim surface with a hairline under it - it was a navy block. The
   wordmark is in Verdana (the only place the brand face is used); its layout is in the `wac-structure` block. */
.bar { padding: 0 var(--space-3); background: var(--surface); color: var(--text); border-bottom: 1px solid var(--line); }
.brand { font-family: var(--font-brand); font-size: var(--text-md); font-weight: var(--weight-strong); letter-spacing: 0; }
main { max-width: 980px; margin: 0 auto; padding: 18px 20px 60px; }
.foot { max-width: 980px; margin: 0 auto; padding: 0 20px 30px; color: var(--muted); font-size: var(--text-xs); }
/* The theme control (AppTheme): three icon buttons, the chosen one pressed. W2 moves it into the top bar. */
.themepick { display: inline-flex; gap: 2px; margin-left: 10px; vertical-align: middle; }
.themepick .btn { color: var(--muted); }
.themepick .btn[aria-pressed="true"] { color: var(--accent); background: var(--accent-soft); }

/* ── The navigation rail (`AppRail`; the business look wave B, 2026-10-13) ─────────────────────────────────────────
   Chats / Overview / Tasks / Revenue / Sent were a row of tabs above the chat list. They are ONE `<nav class="tabs rail">`
   now: HERE, the bar fixed to the bottom of the window (every width below the three panes - five equal columns, each a
   56px target, the count on the icon); in the three panes (>= 1100px) the 64px column at the far left (`wac-structure`
   block at the end of this file). An item is still `a.tab`, its count still its `<i>`. */
.tabs.rail { position: fixed; left: 0; right: 0; bottom: 0; z-index: 15; display: flex; margin: 0;
             padding: 0 0 env(safe-area-inset-bottom, 0px); background: var(--surface); border-top: 1px solid var(--line); }
.rail .tab { position: relative; flex: 1 1 0; min-width: 0; min-height: 56px; display: flex; flex-direction: column; align-items: center;
             justify-content: center; gap: 3px; padding: 0; text-decoration: none; color: var(--muted); background: transparent;
             border-radius: 0; font-size: var(--text-xs); line-height: 14px; }
.rail .tab:hover { color: var(--text); }
.rail .tab.on { color: var(--accent); background: var(--accent-soft); font-weight: var(--weight-strong); }
.rail .tab.on::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px; background: var(--accent); }
.rail .tab:focus-visible { outline: var(--focus-ring); outline-offset: -2px; border-radius: var(--radius-sm); }
.railico { position: relative; display: inline-flex; }
.railico svg { display: block; }
.raillbl { max-width: 100%; white-space: nowrap; }
.rail .tab i { position: absolute; top: -6px; left: 14px; min-width: 18px; height: 16px; padding: 0 3px; box-sizing: border-box;
               font-style: normal; font-size: var(--text-xs); font-weight: var(--weight-strong); line-height: 14px; text-align: center;
               border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--page); color: var(--muted); }
.rail .tab.on i { background: var(--surface); border-color: var(--accent-line); color: var(--accent); }
/* The page keeps room for the bar (`AppMain.MarkRail` says when one is drawn - never on sign-up, sign-in or a refusal). */
html.wac-rail body { padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px)); }

/* ⚠ The coverage line is load-bearing, not chrome: while most chats are unread, every list on
   every screen is a partial answer and must say so. It disappears by itself at full coverage. */
.cover {
    margin: 0 0 14px; padding: 9px 13px; border-radius: var(--radius-md); font-size: var(--text-sm);
    background: var(--warning-soft); color: var(--warning-ink); border: 1px solid var(--warning-line);
}
.none, .wait, .trouble, .signedout { color: var(--dim); font-size: var(--text-base); }
.trouble { color: var(--owed); }
/* ⚠ SIGNED OUT IS NOT TROUBLE, AND IT MUST NOT BE ORANGE (found in a browser 2026-09-24). A browser
   that has never signed in is the ORDINARY state of a public page - most visitors are in it - and
   `AppSignUp`'s own header already ruled that orange body text "reads as breakage". So the sentence
   is ink, not alarm, and the green button beside it carries the emphasis. */
.signedout { color: var(--ink); font-size: var(--text-md); max-width: 60ch; }

/* ── Search on Chats ──────────────────────────────────────────────────────────────────────────
   ⚠ 16px FOR THE SAME REASON AS THE SIGN-UP FIELDS: Mobile Safari zooms the page when a smaller
   field takes focus. The label is real text (visually small), not a placeholder standing in for
   one - a placeholder vanishes the moment somebody types. */
.find { margin: 0 0 12px; }
.find label { display: block; font-size: var(--text-xs); font-weight: var(--weight-strong); color: var(--muted); margin: 0 0 5px; }
/* ⚠ THE SEARCH LABELS ARE `.sr` SINCE 2026-09-28 - still real labels for a screen reader, no longer a row of
   the screen (owner: explanations belong in a tooltip). The rule above stays for any visible find label. */
.find label.sr { margin: 0; }
.find input {
    width: 100%; max-width: 420px; font: inherit; font-size: var(--text-md); color: var(--ink);
    background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-md); padding: 9px 12px;
}
.find input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.found { margin: 6px 0 0; font-size: var(--text-sm); color: var(--dim); min-height: 1em; }
/* ⭐ THE BOX IS THE MAIN SCREEN'S SEARCH (2026-09-25) and sits above the tabs, so it gets the width
   of a search field rather than a filter's. `min-width: 0` keeps it inside a 390px screen. */
.find input { max-width: 560px; min-width: 0; }

/* ── The message hits under the cards ────────────────────────────────────────────────────────
   ⚠ Its OWN element (`wac-msghits`), rewritten in place - never the box above it. Ink throughout:
   "being prepared" and "no messages match" are states, not faults. */
.msghits { margin: 18px 0 0; }
.hitshead { margin: 0 0 4px; font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--muted); }
.hitsay { margin: 0 0 8px; font-size: var(--text-sm); color: var(--ink); }
.hits { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.hit a { display: block; padding: 9px 13px; text-decoration: none; color: inherit; background: var(--bg);
         border: 1px solid var(--line); border-radius: var(--radius-lg); min-width: 0; }
.hit a:hover { background: var(--soft); }
.hit .who { font-weight: 600; margin-right: 8px; }
.hit .when { font-size: var(--text-xs); color: var(--dim); }
.hit .snip { display: block; margin-top: 3px; font-size: var(--text-base); overflow-wrap: anywhere; }
.hit .older { display: block; margin-top: 3px; font-size: var(--text-xs); color: var(--dim); font-style: italic; }
.hit mark { background: var(--mark); color: var(--on-mark); border-radius: var(--radius-sm); padding: 0 1px; }

/* ── The notice after a save ("that worked"), at the top of the main screen ──────────────────── */
.notice { margin: 0 0 12px; padding: 9px 13px; border-radius: var(--radius-md); font-size: var(--text-base);
          color: var(--ink); background: var(--brandsoft); border: 1px solid var(--brandline); }

/* ── The error handler's one line (`AppErrorBanner`): drawn once, when the first error was reported ─── */
.wac-fault { margin: 12px 16px 0; padding: 9px 13px; border-radius: var(--radius-md); font-size: var(--text-base);
             color: var(--ink); background: var(--bg); border: 1px solid var(--line); }

/* ── Contact cards ────────────────────────────────────────────────────────────────────────── */
.cards { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px;
         grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.card { background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-lg);
        border-left: 3px solid var(--quiet); }
.card.s-owed { border-left-color: var(--owed); }
.card.s-active { border-left-color: var(--live); }
.card a { display: grid; grid-template-columns: 44px minmax(0, 1fr); column-gap: 12px; align-items: start;
          padding: 11px 14px; text-decoration: none; color: inherit; }
/* The contact's WhatsApp picture (AppAvatars): a grey disc with initials, the picture laid on as its
   background once fetched - so a missing or failed picture is WhatsApp's own plain disc, never a
   broken image. */
.av { display: flex; align-items: center; justify-content: center; width: 44px; height: 44px;
      border-radius: 50%; background: var(--disc) center / cover no-repeat; color: var(--disc-ink);
      font-size: var(--text-md); font-weight: var(--weight-strong); line-height: 1; flex: none; overflow: hidden; }
.av.has { color: transparent; }
.av.sm { display: inline-block; width: 18px; height: 18px; vertical-align: -4px; margin-right: 6px; }
/* The silhouette of a contact with no name (AppAvatarFace): the whole disc, in its text colour, so a
   picture laid on (.has, transparent text) hides it like the initials. */
.av svg { display: block; }
.card .av { grid-row: 1 / 4; }
.card .t, .card .r, .card .m { grid-column: 2; }
.card a:hover { background: var(--soft); }
.card .t { display: block; font-weight: 600; }
.card .r { display: block; font-size: var(--text-sm); color: var(--dim); margin-top: 2px;
           overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card .r.unread { font-style: italic; }
.card .m { display: block; font-size: var(--text-xs); color: var(--muted); margin-top: 6px; }
/* A count on a row: a small rectangle with an edge (it was a pill). The red "N to do" keeps its colours. */
.badge { font-style: normal; font-weight: var(--weight-strong); background: var(--danger-soft); color: var(--danger);
         border: 1px solid var(--danger-line); border-radius: var(--radius-sm); padding: 0 5px; white-space: nowrap; }
/* ⭐ "N new" - their messages since the page loaded (AppLiveList): the accent count, not the alarm red. */
.badge.fresh { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.card.isnew .t::after { content: "New chat"; font-weight: var(--weight-regular); font-size: var(--text-xs); color: var(--muted);
                        margin-left: 8px; }

/* ── Task and money rows ──────────────────────────────────────────────────────────────────── */
.rows { list-style: none; margin: 0; padding: 0; }
.row { border-top: 1px solid var(--line); background: var(--bg); }
.row:first-child { border-top: 0; border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
.row:last-child { border-radius: 0 0 var(--radius-lg) var(--radius-lg); }
.row a { display: grid; grid-template-columns: 170px 1fr 190px; gap: 14px; align-items: baseline;
         padding: 10px 14px; text-decoration: none; color: inherit; }
.row a:hover { background: var(--soft); }
.row .l { font-size: var(--text-sm); color: var(--muted); font-weight: var(--weight-strong); }
.row .v { overflow-wrap: anywhere; }
.row .w { font-size: var(--text-sm); color: var(--dim); text-align: right;
          overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sums { margin: 0 0 12px; font-size: var(--text-base); }
.sums b { font-size: var(--text-md); }
.sums i { color: var(--dim); font-size: var(--text-xs); font-style: normal; }

/* ── One contact: the chat card and the AI panel (2026-09-25) ─────────────────────────────────
   ⭐ THE CHAT IS THE PRIMARY ELEMENT (owner, 2026-09-25: the conversation fell below the fold). The
   frame and the composer are ONE card, the way WhatsApp draws a chat: the frame's bottom edge meets
   the composer bar with a shared border and no gap. The AI panel is a column beside it on a wide
   screen and a bottom sheet on a narrow one.

   ⚠ ONE SCROLLBAR PER JOB, STILL. On the contact route the app page and the frame both scrolled
   the same content - two bars side by side, each moving the page, neither reaching the end.
   `detail` turns the app into a fixed-height shell: the WINDOW stops scrolling and the card takes
   whatever height is left. `min-height: 0` on every flex child is the load-bearing line: a flex
   item defaults to `min-height: auto`, which refuses to shrink below its content, so the frame
   would overflow the shell and put the window's scrollbar straight back.
   ⚠ `100dvh` AFTER `100vh`: on a phone `vh` is the height with the browser's toolbars HIDDEN, so a
   `100vh` shell pushes the composer under the address bar - exactly the control that must always
   show. Browsers without `dvh` keep the `vh` line. */
.back { margin: 0 0 10px; }
.back .btn { margin-left: -8px; }
/* "Export chat (CSV)" (`AppExportRules.Control`): right of "All chats" on the same line; its note ticks the
   progress and says the result in ink (law 7). It wraps under the link on a narrow phone. */
.back.hasexport { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 12px; }
.xport { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 2px 10px; min-width: 0; }
.xportbtn[disabled] { cursor: progress; }
.xportnote { font-size: var(--text-sm); line-height: 1.4; color: var(--ink); overflow-wrap: anywhere; }
.xportnote:empty { display: none; }
/* The frame is the contact page itself - not a re-implementation of it. */
#wac-frame { width: 100%; height: 80vh; border: 1px solid var(--line); border-radius: var(--radius-lg);
             background: var(--bg); display: block; }

body.detail { height: 100vh; height: 100dvh; overflow: hidden; display: flex; flex-direction: column; }
body.detail .bar { flex: none; padding: 10px 16px; }
/* ⚠ THE FOOTER'S LINKS LIVE ON THE LIST SCREENS; on a chat every pixel goes to the conversation. */
body.detail .foot { display: none; }
/* ⚠ `width: 100%` IS REQUIRED HERE, and it is not redundant with `max-width`. `main` carries
   `margin: 0 auto` to centre itself, and an auto cross-axis margin on a FLEX ITEM overrides the
   default `align-items: stretch` - so the moment `body` became a flex column, `main` collapsed to
   its content width and the frame rendered as a ~300px ribbon down the middle of the window. */
body.detail main { flex: 1; min-height: 0; width: 100%; max-width: 1440px; display: flex;
                   flex-direction: column; padding: 8px 16px 12px; }
body.detail .back { flex: none; margin: 0 0 6px; }
body.detail .relink { flex: none; }

.contact { flex: 1; min-height: 0; display: flex; gap: 14px; }
.chatcard {
    flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column;
    background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden;
}
body.detail #wac-frame { flex: 1; min-height: 0; height: auto; border: 0; border-radius: 0; }
/* The one shared border between the thread and whatever follows it. */
.chatcard #wac-frame + * { border-top: 1px solid var(--line); }

/* The AI chips: in the AI panel, just above its Ask box (owner, 2026-09-26: "two ai areas - combine the
   bottom one into the right one") - there before the first question and after every answer.
   ⭐ ONE SCROLLING ROW (owner, 2026-09-30: "Yes, one scrolling row"): they no longer wrap into a wall of
   pills above the Ask box. The row scrolls sideways (finger, trackpad, Shift+wheel), snaps to a chip, and
   each chip is a real button, so Tab walks them and the browser scrolls the focused one into view. A soft
   fade at the right edge says there is more; the mask needs no colour, so it is right in both themes. */
.chips {
    flex: none; display: flex; flex-wrap: nowrap; gap: var(--space-2); padding: 10px 12px 4px;
    overflow-x: auto; overflow-y: hidden; scroll-snap-type: x proximity; scroll-padding: 0 12px;
    overscroll-behavior-x: contain; scrollbar-width: thin;
    border-top: 1px solid var(--line);
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
    mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
}
.chips::after { content: ""; flex: none; width: 16px; }
.chips + .aask { border-top: 0; }
.chip {
    flex: none; scroll-snap-align: start; white-space: nowrap; font: inherit; font-size: var(--text-sm);
    font-weight: var(--weight-strong); line-height: 1.3;
    color: var(--accent); background: var(--accent-soft); border: 1px solid var(--accent-line);
    border-radius: var(--radius-sm); padding: 5px 10px; cursor: pointer;
}
.chip:hover { border-color: var(--accent); }
.chip:focus-visible { outline: var(--focus-ring); outline-offset: -2px; }
.spark { color: var(--accent); margin-right: 6px; }

/* ── The composer bar ─────────────────────────────────────────────────────────────────────────
   ⚠ IT SITS OUTSIDE THE FRAME, NOT INSIDE IT. The frame holds the contact page, which is a RECORD
   the generator owns and the owner can open straight from Dropbox - putting a control in it would
   mean the page rendered differently depending on who was showing it. It is drawn to LOOK like part
   of the chat, which is a different thing. */
.say { flex: none; display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px; margin: 0;
       padding: 8px 12px 10px; }
.say textarea {
    flex: 1; min-width: 0; resize: none; height: 42px; max-height: 134px; overflow-y: auto;
    font: inherit; font-size: var(--text-md); line-height: 1.5; color: var(--ink); background: var(--surface);
    border: 1px solid var(--line); border-radius: var(--radius-md); padding: 9px 12px;
}
.say textarea:focus { outline: var(--focus-ring); outline-offset: 1px; border-color: var(--accent); background: var(--surface); }
/* Send and the AI button are the family's own (`btn send`, `btn secondary icon`): the box's height and its 6px corners.
   ⭐ Send is the accent, like every primary button (2026-10-13 - it was WhatsApp green and a pill); the AI button is a
   SQUARE icon button on the accent tint (it was a round one). */
.say .btn { flex: none; height: 42px; border-radius: var(--radius-md); }
.say .btn.send { padding: 0 18px; }
.aibtn.btn { width: 42px; color: var(--accent); background: var(--accent-soft); border-color: var(--accent-line); }
.aibtn.btn[aria-expanded="true"] { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
/* Only what has no bubble to live on: a refusal before sending, a request not written, and the
   sentences that say NOT to send again. Ink; the alarm colour only for a real refusal.
   ⛔ EMPTY UNTIL THERE IS SOMETHING TO SAY (2026-09-28): the standing "Sent from your own WhatsApp number…"
   is a tooltip on Send (`AppComposerView.Bar`), never a line taking the chat's space. */
.sayhint { flex-basis: 100%; margin: 0; padding: 0 6px; font-size: var(--text-xs); line-height: 1.4; color: var(--dim); }
.sayhint:empty { display: none; }
.sayhint.bad { color: var(--owed); }
.sayback { margin: 5px 0 0; font-size: var(--text-xs); color: var(--dim); }
.sayback.bad { color: var(--owed); }
.sayoff { flex: none; display: flex; align-items: center; gap: 10px; margin: 0; padding: 10px 12px;
          font-size: var(--text-sm); color: var(--dim); }
.sayback code, .sayoff code { font-size: var(--text-xs); background: var(--soft); padding: 1px 4px;
                              border-radius: var(--radius-sm); }

/* ── The AI panel ─────────────────────────────────────────────────────────────────────────────
   ⚠ FIRST-CLASS, NOT A POPUP: a tinted header with the ✦, and the answers read like a
   conversation. ⛔ A DRAFT IS DRAWN AS THE MESSAGE IT WOULD BE, with Send (the owner's click sends
   it through the composer's own `/send` path), Edit (fills the composer) and Copy. An answer's
   simple Markdown arrives as `p`/`ul`/`ol`/`strong`/`em`/`code`/`br` elements whose words are set
   as TEXT (`AppAssistPanel`); questions and drafts stay `white-space: pre-wrap` text. */
.assist, .aiscrim { display: none; }
.contact.ai-on .assist {
    display: flex; flex-direction: column; flex: none; width: 360px; min-height: 0;
    background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden;
}
.ahead {
    flex: none; display: flex; align-items: center; gap: 2px; padding: 11px 10px 11px 14px;
    background: var(--surface); border-bottom: 1px solid var(--line);
}
.ahead .spark { font-size: var(--text-md); }
/* The contact's disc beside "Ask about <name>" (their picture, initials or the silhouette). */
.ahead .av { width: 28px; height: 28px; font-size: var(--text-xs); margin: 0 8px 0 6px; }
.ahead h2 { flex: 1; min-width: 0; margin: 0; font-size: var(--text-base); font-weight: var(--weight-strong); color: var(--text);
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.aclose.btn { flex: none; color: var(--muted); }
.aclose.btn:not(:disabled):hover { background: var(--accent-soft); color: var(--accent); }
/* Share the available height with task suggestions; both regions scroll before fixed AI controls are clipped. */
.alog { flex: 1 1 50%; min-height: 0; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 10px; }
.aintro { font-size: var(--text-base); line-height: 1.5; color: var(--dim); }
.aq {
    align-self: flex-end; max-width: 88%; padding: 8px 12px; font-size: var(--text-base); line-height: 1.45;
    color: var(--ink); background: var(--aisoft); border: 1px solid var(--ailine); border-radius: var(--radius-md);
    white-space: pre-wrap; overflow-wrap: anywhere;
}
.aa { font-size: var(--text-base); line-height: 1.55; color: var(--ink); overflow-wrap: anywhere; }
.aa p, .aa ul, .aa ol { margin: 0 0 8px; }
.aa > :last-child { margin-bottom: 0; }
.aa ul, .aa ol { padding-left: 20px; }
.aa li { margin: 2px 0; }
.aa strong { font-weight: var(--weight-strong); }
.aa code { font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; font-size: var(--text-sm);
           background: var(--soft); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 0 4px; }
/* Tables and charts in an answer (`AppAssistRich`; built element by element by `AppAssistPanel`).
   ⚠ A WIDE TABLE SCROLLS INSIDE ITS OWN `.atable`, NEVER THE PAGE - at 390px a four-column table is
   wider than the panel. `.aa`'s `overflow-wrap: anywhere` is undone inside a table, or every cell
   would break mid-word to fit rather than scroll. ⚠ Words wear ink; colour belongs to the marks. */
.aa .atable { overflow-x: auto; max-width: 100%; margin: 0 0 8px; border: 1px solid var(--line); border-radius: var(--radius-md); }
.aa table { border-collapse: collapse; min-width: 100%; font-size: var(--text-sm); line-height: 1.4; overflow-wrap: normal; }
.aa th, .aa td { padding: 6px 10px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--line);
                 min-width: 5ch; max-width: 32ch; }
.aa th { font-weight: var(--weight-strong); white-space: nowrap; background: var(--soft); }
.aa tbody tr:nth-child(even) td { background: var(--soft); }
.aa tbody tr:last-child td { border-bottom: 0; }
.aa [data-align="center"] { text-align: center; }
.aa [data-align="right"], .aa .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.aa tr.total td, .aa td.total, .aa th.total { font-weight: var(--weight-strong); border-top: 2px solid var(--line); }
.aa .muted, .amuted { color: var(--dim); }
.amuted { font-size: var(--text-sm); line-height: 1.45; }
.aa .achart { max-width: 100%; margin: 0 0 8px; color: var(--ink); }
/* `fill: currentColor` is only the INHERITED default: an unclassed shape is ink, not black-on-dark. */
.aa .achart svg { width: 100%; height: auto; display: block; fill: currentColor; }
.aa .achart .c1 { fill: var(--viz1); } .aa .achart .c2 { fill: var(--viz2); } .aa .achart .c3 { fill: var(--viz3); }
.aa .achart .c4 { fill: var(--viz4); } .aa .achart .c5 { fill: var(--viz5); } .aa .achart .c6 { fill: var(--viz6); }
/* A line is a stroke: `line`, `polyline` and an unfilled `path` take the slot's colour as stroke. */
.aa .achart line.c1, .aa .achart polyline.c1, .aa .achart path.c1[fill="none"] { fill: none; stroke: var(--viz1); }
.aa .achart line.c2, .aa .achart polyline.c2, .aa .achart path.c2[fill="none"] { fill: none; stroke: var(--viz2); }
.aa .achart line.c3, .aa .achart polyline.c3, .aa .achart path.c3[fill="none"] { fill: none; stroke: var(--viz3); }
.aa .achart line.c4, .aa .achart polyline.c4, .aa .achart path.c4[fill="none"] { fill: none; stroke: var(--viz4); }
.aa .achart line.c5, .aa .achart polyline.c5, .aa .achart path.c5[fill="none"] { fill: none; stroke: var(--viz5); }
.aa .achart line.c6, .aa .achart polyline.c6, .aa .achart path.c6[fill="none"] { fill: none; stroke: var(--viz6); }
/* Chart text is ink whatever the model coloured it (this rule outranks `.c1`..); axes and gridlines
   marked `muted` recede. */
.aa .achart text, .aa .achart tspan, .aa .achart text[class], .aa .achart tspan[class] { fill: currentColor; stroke: none; }
.aa .achart .muted, .aa .achart text.muted, .aa .achart tspan.muted { fill: var(--dim); }
.aa .achart line.muted, .aa .achart polyline.muted, .aa .achart path.muted[fill="none"] { fill: none; stroke: var(--dim); }
/* Taking a block out (`AppAssistExport`, 2026-09-27): each table/chart sits in an `.ablock` with a quiet
   tools row under it - a small text button on the right ("Copy table", "Save chart") and the block's own
   note line (ink, never orange: a refused clipboard is the browser's answer). A bar with a `<title>` is a
   mark: pointer, a focus ring, and its title on the `.avalue` line when tapped. */
.aa .ablock { margin: 0 0 8px; }
.aa .ablock > .atable, .aa .ablock > .achart { margin: 0; }
.atools { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 2px 10px; margin: 2px 0 0; }
.atools .anote { flex: 1 1 60%; min-width: 0; font-size: var(--text-sm); line-height: 1.4; color: var(--ink); text-align: right; }
.atools .anote:empty { display: none; }
/* The tools are `btn ghost sm`; "Keep this" (`AppAssistKeep`) once kept reads "Kept" - quiet, not a control any more. */
.atool.btn:disabled { color: var(--muted); opacity: 1; }
.avalue { font-size: var(--text-sm); line-height: 1.4; color: var(--ink); margin: 4px 0 0; padding: 4px 8px;
          background: var(--soft); border: 1px solid var(--line); border-radius: var(--radius-md); width: fit-content; max-width: 100%; }
.avalue:empty { display: none; }
.aa .achart [data-wac-mark] { cursor: pointer; }
.aa .achart [data-wac-mark]:focus:not(:focus-visible) { outline: none; }
.aa .achart [data-wac-mark]:focus-visible, .aa .achart [data-wac-on="1"] { stroke: currentColor; stroke-width: 2px; }
.awhy {
    font-size: var(--text-base); line-height: 1.5; color: var(--ink); background: var(--soft);
    border: 1px solid var(--line); border-radius: var(--radius-md); padding: 9px 12px; white-space: pre-wrap;
}
.awhy .abtn { display: block; margin-top: 8px; }
/* A question that got no answer keeps the words that had streamed (`AppAssistEntry.Partial`), marked as unfinished. */
.aa.apartial { border-left: 3px solid var(--line); padding-left: 9px; color: var(--dim); }
/* Chat list actions stay beside the navigation link and remain reachable by keyboard. */
.cards > .card { position: relative; }
.cards > .card > a { padding-right: 58px; }
.cards.asrows > .card > a, .paneleft .cards.asrows > .card > a { padding-right: 58px; }
.chatquick-open {
    position: absolute; right: 8px; top: 8px; width: 44px; height: 44px;
}
.adraft { flex: none; border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 10px; background: var(--page); }
.adlabel { font-size: var(--text-xs); color: var(--dim); margin: 0 0 7px; }
.adbubble {
    margin-left: auto; width: fit-content; max-width: 100%; padding: 8px 12px; font-size: var(--text-base);
    line-height: 1.45; color: var(--ink); background: var(--mebubble); border: 1px solid var(--accent-line); border-radius: var(--radius-md);
    white-space: pre-wrap; overflow-wrap: anywhere;
}
.aacts { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin: 9px 0 0; }
.adwhy { font-size: var(--text-sm); line-height: 1.45; color: var(--ink); margin: 8px 0 0; }
.adsent { font-size: var(--text-sm); line-height: 1.45; color: var(--dim); margin: 7px 0 0; text-align: right; }
.athink { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; font-size: var(--text-sm); color: var(--dim); }
.athinkline { flex: 1; min-width: 150px; }
.adots { width: 26px; height: 8px; flex: none;
         background: radial-gradient(circle, var(--ai) 3px, transparent 3.5px) 0 0 / 9px 8px repeat-x;
         animation: wacdots 1s steps(3) infinite; }
@keyframes wacdots { from { width: 8px; } to { width: 35px; } }
/* ⭐ A TABLE OR CHART STILL BEING WRITTEN (`AppAssistPanel.Slot`): a quiet box where it will stand, its kind
   in words and moving dots - an honest "in progress", never a percentage. A chart's box is chart-shaped. */
.aslot { display: flex; align-items: center; gap: 10px; min-height: 64px; margin: 0 0 8px; padding: 12px 14px;
         font-size: var(--text-sm); color: var(--dim); background: var(--soft); border: 1px dashed var(--line);
         border-radius: var(--radius-md); box-sizing: border-box; }
.aslot[data-kind="chart"] { min-height: 140px; }
.aslotline { flex: 1; min-width: 0; }
.anote { font-size: var(--text-sm); color: var(--ink); }
/* ⭐ THE SAVED HISTORY (`ai-chat.json`): a quiet line per day, a closed task's record in its own tint,
   what became of an older draft, and how the history stands when it is not simply there (ink, never orange). */
.aday { align-self: center; font-size: var(--text-xs); font-weight: 600; color: var(--dim); background: var(--soft);
        border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 1px 8px; }
.aq.atask { background: var(--soft); border: 1px solid var(--line); color: var(--dim); }
.adout { font-size: var(--text-sm); line-height: 1.45; color: var(--dim); margin: 7px 0 0; text-align: right; }
.ahist { display: flex; align-items: center; gap: 10px; font-size: var(--text-sm); line-height: 1.45; color: var(--dim); }
/* ⭐ "REMOVE FROM HISTORY" (`AppAssistForget`): a quiet bin under each saved entry, on the LEFT (a draft's Send is on
   the right - a click meant for the bin's confirm never lands on it); pressed, it becomes "Remove this answer?" with
   Remove and Cancel; while the service is asked the entry is faded (`agoing`) and cannot be clicked. */
.aforget { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-start; gap: var(--space-2);
           margin-top: calc(-1 * var(--space-1)); font-size: var(--text-sm); line-height: 1.45; color: var(--muted); }
.aforget .aforgetbtn.btn { color: var(--muted); }
.aforget .aforgetbtn.btn:not(:disabled):hover, .aforget .aforgetbtn.btn:focus-visible { color: var(--danger); }
.aforgetq { color: var(--text); }
.aforgetwhy { flex: 1 1 60%; min-width: 0; color: var(--text); }
.aforgetask { padding: var(--space-2) var(--space-3); background: var(--soft); border: 1px solid var(--line); border-radius: var(--radius-md); }
.aforgetask .aforgetq { flex: 1 1 auto; }
.agoing { opacity: .45; pointer-events: none; transition: opacity .15s; }
.aask { flex: none; display: flex; gap: 8px; align-items: flex-end; padding: 10px 12px; border-top: 1px solid var(--line); }
.aask textarea {
    flex: 1; min-width: 0; resize: none; height: 40px; max-height: 134px; overflow-y: auto;
    font: inherit; font-size: var(--text-md); line-height: 1.45; color: var(--ink); background: var(--surface);
    border: 1px solid var(--line); border-radius: var(--radius-md); padding: 8px 12px;
}
.aask textarea:focus { outline: var(--focus-ring); outline-offset: 1px; border-color: var(--accent); background: var(--surface); }
.ago.btn { flex: none; height: 40px; border-radius: var(--radius-md); }

/* ⚠ BELOW 1100px THE PANEL IS A SHEET OVER THE CHAT (~75% of the height), so the chat card keeps
   the whole width. Must match `AppMain.AssistWideQuery`. */
@media (max-width: 1099px) {
    .contact.ai-on .assist {
        position: fixed; left: 0; right: 0; bottom: 0; z-index: 20; width: auto;
        height: 75vh; height: 75dvh; border-radius: var(--radius-lg) var(--radius-lg) 0 0; border-bottom: 0;
        box-shadow: 0 -8px 30px rgba(16, 20, 28, .18);
    }
    .contact.ai-on .aiscrim { display: block; position: fixed; inset: 0; z-index: 19; background: rgba(16, 20, 28, .35); }
    /* ⭐ The sheet starts shut here, so the ✦ says how many saved questions this chat has. */
    .aibtn { position: relative; }
    .aibtn[data-count]::after {
        content: attr(data-count); position: absolute; top: -3px; right: -3px; min-width: 17px; height: 17px;
        padding: 0 4px; box-sizing: border-box; font-size: var(--text-xs); font-weight: 600; line-height: 17px;
        color: var(--on-accent); background: var(--accent); border: 2px solid var(--surface); border-radius: var(--radius-md);
    }
}

@media (max-width: 620px) {
    body.detail main { padding: 6px 8px 8px; }
    body.detail .bar { padding: 8px 12px; }
    .chatcard { border-radius: var(--radius-lg); }
    .chips { padding: 8px 10px 1px; }
    .say { padding: 7px 8px 8px; gap: 6px; }
    .say .btn.send { padding: 0 14px; }
    /* ⚠ 16px so Mobile Safari does not zoom the page when the box takes focus. */
    .say textarea, .aask textarea { font-size: var(--text-md); }
}

@media (prefers-reduced-motion: reduce) {
    .adots { animation: none; width: 26px; }
}

@media (max-width: 620px) {
    .row a { grid-template-columns: 1fr; gap: 2px; }
    .row .w { text-align: left; }
}

/* ── Account doors: sign in, re-link, Sent, close account (2026-09-24) ──────────────────────────
   ⛔ EVERY SENTENCE HERE IS VALIDATION, SO IT IS INK (`inkwhy`), NEVER `.trouble`'s orange. On the
   close-account page in particular an orange line reads as "something broke while deleting my
   account" - the one impression that page must never give. */
.inkwhy {
    margin: 0 0 14px; padding: 10px 13px; border-radius: var(--radius-md); font-size: var(--text-base); line-height: 1.5;
    color: var(--ink); background: var(--soft); border: 1px solid var(--line);
}
.frozen { margin: 8px 0 0; font-size: var(--text-sm); line-height: 1.5; color: var(--ink); }
.waysin { display: flex; flex-wrap: wrap; gap: 10px; margin: 12px 0 0; }
.foot a { color: var(--dim); }

/* The re-link banner. ⚠ Ink on the soft ground, not an alert colour: the archive is fine. */
.relink {
    margin: 0 0 14px; padding: 12px 14px; border-radius: var(--radius-lg); font-size: var(--text-base); line-height: 1.5;
    background: var(--bg); border: 1px solid var(--line); border-left: 3px solid var(--ink);
}
.relink p { margin: 0 0 10px; }
.relink p:last-child { margin-bottom: 0; }
.relink .btn { white-space: normal; max-width: 100%; }
.relink .progress { color: var(--dim); font-size: var(--text-sm); }
.relink .inkwhy { margin: 10px 0 0; }
.relink .codeslab {
    display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin: 0 0 6px;
    background: var(--brandsoft); border: 1px solid var(--brandline); border-radius: var(--radius-lg); padding: 12px 14px;
}
.relink .codeslab code {
    flex: 1; min-width: 0; word-break: break-all; font-size: var(--text-xl); font-weight: 600;
    letter-spacing: .18em; color: var(--branddeep);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.relink .copied { min-height: 0; margin: 0 0 8px; font-size: var(--text-sm); font-weight: 600; color: var(--live); }
.relink .guide { margin: 0; padding-left: 20px; font-size: var(--text-sm); }

/* The Sent tab. ⚠ `trouble` is ONLY a refused row; "Not confirmed" stays ink - orange would read
   as "send it again", and that is how a message goes twice. */
.sent { list-style: none; margin: 0; padding: 0; }
.sentrow {
    background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 10px 13px;
    margin: 0 0 8px; overflow-wrap: anywhere;
}
.sentrow .when { font-size: var(--text-sm); color: var(--dim); margin-left: 4px; }
.sentrow .state { float: right; font-size: var(--text-xs); font-weight: var(--weight-strong); color: var(--muted); margin-left: 8px; }
.sentrow .preview { margin: 4px 0 0; font-size: var(--text-base); color: var(--ink); white-space: pre-wrap; }
.sentrow .note { margin: 4px 0 0; font-size: var(--text-sm); color: var(--ink); }
.sentrow.trouble { font-size: inherit; color: inherit; border-left: 3px solid var(--owed); }
.sentrow.trouble .state, .sentrow.trouble .note { color: var(--owed); }
#wac-sent-head[hidden] { display: none; }

/* Close my account + sign in: the sign-up card, with a few parts of their own. */
.signup .flabel { display: block; margin: 12px 0 6px; font-size: var(--text-sm); font-weight: 600; color: var(--ink); }
.signup .kept { margin: 0 0 14px; font-size: var(--text-base); line-height: 1.5; }
.signup .plan { margin: 0 0 8px; padding-left: 20px; font-size: var(--text-base); line-height: 1.5; }
.signup .target { margin: 0 0 8px; font-size: var(--text-base); }
.signup .closed { margin: 0; font-size: var(--text-lg); font-weight: var(--weight-strong); }
.signup .steps { list-style: none; margin: 0 0 14px; padding: 0; font-size: var(--text-sm); line-height: 1.45; }
.signup .lstep { display: flex; gap: 9px; align-items: flex-start; padding: 5px 0; }
.signup .lmark {
    flex: none; width: 20px; height: 20px; border-radius: 50%; margin-top: 1px;
    display: flex; align-items: center; justify-content: center; font-size: var(--text-xs); font-weight: var(--weight-strong);
}
.signup .lstep.done .lmark { background: var(--success); color: var(--surface); }
.signup .lstep.failed .lmark { background: var(--ink); color: var(--bg); }
.signup .lstep.todo .lmark { border: 2px solid var(--dim); color: var(--dim); }
.signup .progress:not(:empty) { display: block; }
.signup .waysin .btn { flex: 1 1 auto; }
/* The owner's Claude analysis panel (#claude). Ink throughout - every sentence is validation. */
.signup .tokenstate { margin: 12px 0 0; font-size: var(--text-base); font-weight: 600; color: var(--ink); }
.signup.claude .fieldrow input { min-width: 0; font-family: inherit; }
.signup.claude code { overflow-wrap: anywhere; }


/* ══ Sign-up (`#signup`) ═══════════════════════════════════════════════════════════════════════
   ⚠ EVERY RULE BELOW IS SCOPED UNDER `.signup`, AND THE SCOPE IS LOAD-BEARING. The rest of this
   stylesheet dresses a surface that is BROWSED (the note at the top of this file): rows scanned
   down the left edge, nothing centred, nothing decorated. Sign-up is the opposite surface - one
   stranger, one instruction at a time, and the only page in this product somebody meets before
   they trust it. The two need different rules, and the scope is what lets them share one file
   without either drifting into the other.

   ⚠ IT REUSES THE TOKENS RATHER THAN A SECOND PALETTE. The visitor sees this page and then the
   contact list; a sign-up with its own greens and greys would read as two products glued together,
   which is the same reason the contact page and the app share a palette to begin with.
   ───────────────────────────────────────────────────────────────────────────────────────────── */
.signup { max-width: 560px; margin: 6px auto 0; }

.signup .panel {
    position: relative; overflow: hidden;
    background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-lg);
    padding: 26px 28px 30px;
}
/* The one piece of pure decoration here, and it earns its place: it is what stops a white box on
   a grey ground reading as an unstyled form. */
.signup .panel::before {
    content: ""; position: absolute; inset: 0 0 auto; height: 3px;
    background: linear-gradient(90deg, var(--accent-hover), var(--accent));
}

/* ⚠ The crown is only the step counter since 2026-10-01 (the logo and wordmark repeated the top bar). */
.signup .crown { margin: 0 0 var(--space-3); }
.signup .eyebrow { margin: 0; font-size: var(--text-xs); font-weight: var(--weight-strong); color: var(--muted); }

/* ── The progress trail ────────────────────────────────────────────────────────────────────── */
.signup .wiz { list-style: none; display: flex; margin: 0 0 22px; padding: 0; }
.signup .wstep {
    flex: 1; min-width: 0; position: relative;
    display: flex; flex-direction: column; align-items: center; gap: 7px;
}
/* ⚠ THE CONNECTOR BELONGS TO THE STEP ON ITS RIGHT, not to the one on its left. That is what
   lets the track be coloured by a step's OWN state - green up to where the visitor has actually
   got - with no rule that has to reach sideways to a sibling it cannot select. */
.signup .wstep + .wstep::before {
    content: ""; position: absolute; top: 13px; right: 50%; left: -50%; height: 2px;
    background: var(--line);
}
.signup .wstep.done::before, .signup .wstep.now::before { background: var(--live); }
.signup .wdot {
    position: relative; z-index: 1; width: 28px; height: 28px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: var(--text-sm); font-weight: var(--weight-strong);
    background: var(--bg); border: 2px solid var(--line); color: var(--dim);
}
.signup .wstep.done .wdot { background: var(--live); border-color: var(--live); color: var(--surface); }
.signup .wstep.now .wdot { border-color: var(--live); color: var(--live); }
.signup .wstep.now .wdot::after {
    content: ""; position: absolute; inset: -5px; border-radius: 50%;
    border: 2px solid var(--live); animation: wacpulse 2.2s ease-out infinite;
}
.signup .wname { font-size: var(--text-xs); line-height: 1.3; text-align: center; color: var(--dim); }
.signup .wstep.now .wname { color: var(--ink); font-weight: 600; }
@keyframes wacpulse {
    0% { transform: scale(.8); opacity: .55; }
    70% { transform: scale(1.3); opacity: 0; }
    100% { transform: scale(1.3); opacity: 0; }
}

/* ── Type ──────────────────────────────────────────────────────────────────────────────────── */
.signup h1, .signup h2 { margin: 0 0 8px; font-size: var(--text-lg); font-weight: var(--weight-strong); line-height: 1.25; }
.signup p { margin: 0 0 14px; }
.signup .lede { margin: 0 0 16px; color: var(--dim); font-size: var(--text-base); }
.signup .ask { margin: 0 0 9px; font-weight: 600; }
.signup .hint { margin: 14px 0 0; font-size: var(--text-sm); line-height: 1.5; color: var(--dim); }
.signup code {
    font-size: var(--text-sm); background: var(--soft); border-radius: var(--radius-sm); padding: 1px 5px;
    overflow-wrap: anywhere;
}

/* ── Numbered instructions ─────────────────────────────────────────────────────────────────── */
.signup .guide { list-style: none; counter-reset: wacg; margin: 0 0 16px; padding: 0; }
.signup .guide li {
    position: relative; counter-increment: wacg; padding: 0 0 14px 34px;
    font-size: var(--text-base); line-height: 1.5;
}
.signup .guide li:last-child { padding-bottom: 0; }
.signup .guide li::before {
    content: counter(wacg); position: absolute; left: 0; top: 1px;
    width: 23px; height: 23px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: var(--text-xs); font-weight: var(--weight-strong); background: var(--accent-soft); color: var(--accent);
}

/* ── Fields and buttons ────────────────────────────────────────────────────────────────────── */
.signup .fieldrow { display: flex; align-items: stretch; gap: 10px; margin: 0 0 2px; }
.signup .fieldrow.one { display: block; }
.signup .fieldrow.one .btn { width: 100%; }
/* ⚠ 16px IS NOT A TASTE DECISION. Mobile Safari ZOOMS the whole page when a field smaller than
   16px takes focus, and half this product's visitors arrive on a phone holding the handset the
   next step is about - a page that jumps as they tap is one they have to pinch back out of. */
.signup .fieldrow input {
    flex: 1; min-width: 0; font: inherit; font-size: var(--text-md); color: var(--ink);
    background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-md); padding: 11px 13px;
}
.signup .fieldrow input::placeholder { color: var(--dim); opacity: 1; }
.signup .fieldrow input:focus {
    outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
}
/* ⛔ NOT SCOPED TO `.signup`, AND THE SCOPING WAS A REAL BUG (found in a browser 2026-09-24).
   These rules lived under `.signup`, so the "Sign up" link the signed-out archive routes offer
   rendered as bare underlined text - the one control on a dead-end page, looking like nothing.
   ⚠ Nothing outside the sign-up card used `.btn` before, so making it global changes no existing
   pixel; it just stops the next button outside that card being invisible for the same reason. */
/* ══ THE ONE BUTTON FAMILY (design system, owner 2026-09-30: "Do the one button style") ═════════════════
   `.btn` alone (or `.btn.primary`) = the main action: accent fill. `.secondary` = outline, `.ghost` = words only,
   `.danger` = destructive, `.send` = WhatsApp green and ONLY for a send. `.sm` = the compact size, `.icon` = a
   square icon-only button, which always carries an aria-label. One focus ring for all: 2px accent, offset 2.
   Older class names on the same elements (`abtn`, `ago`, `atool`, `trgo` ...) are hooks for code and tests now,
   not looks. */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
    min-height: 36px; font: inherit; font-size: var(--text-base); font-weight: var(--weight-strong); line-height: 1.2;
    text-decoration: none; white-space: nowrap; cursor: pointer;
    color: var(--on-accent); background: var(--accent); border: 1px solid var(--accent);
    border-radius: var(--radius-md); padding: 7px 16px;
}
.btn:not(:disabled):hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.btn.secondary { color: var(--accent); background: var(--surface); border-color: var(--line); }
.btn.secondary:not(:disabled):hover { background: var(--accent-soft); border-color: var(--accent-line); }
.btn.ghost { color: var(--accent); background: transparent; border-color: transparent; padding-left: 8px; padding-right: 8px; }
.btn.ghost:not(:disabled):hover { background: var(--accent-soft); border-color: transparent; }
.btn.danger { color: var(--surface); background: var(--danger); border-color: var(--danger); }
.btn.danger:not(:disabled):hover { background: var(--danger); border-color: var(--danger); filter: brightness(.92); }
/* ⭐ `.send` IS THE PRIMARY LOOK (2026-10-13, the business look): the kalmstrom accent, no WhatsApp green. The class
   stays as a hook (the composer, the draft cards, the harness); the rule is written out so the look is stated, not
   merely inherited. */
.btn.send { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }
.btn.send:not(:disabled):hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn.sm { min-height: 30px; padding: 4px 12px; font-size: var(--text-sm); }
.btn.ghost.sm { padding-left: 6px; padding-right: 6px; }
.btn.icon { width: 36px; padding: 0; }
.btn.icon.sm { width: 30px; min-width: 30px; padding: 0; }
.btn svg.wi { width: 16px; height: 16px; }
.btn:disabled { opacity: .55; cursor: default; }
.btn .spin {
    width: 14px; height: 14px; border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, .45); border-top-color: #fff;
    animation: wacspin 800ms linear infinite;
}
.signup .aside { display: block; margin-top: 7px; font-size: var(--text-sm); color: var(--dim); }
/* ⛔ A BUTTON INSIDE THE CARD WRAPS; IT NEVER CLIPS. The global `.btn` is `nowrap` (right for the
   short labels elsewhere), and "Open Dropbox and allow WACommander" inside a numbered guide item is
   wider than a 390px card - it overflowed and was cut off by the panel's `overflow: hidden`, on the
   one control the Dropbox step depends on. */
.signup .btn { white-space: normal; max-width: 100%; text-align: center; line-height: 1.3; min-height: 44px; padding: 10px 20px; font-size: var(--text-md); }
.signup .btn.sm { min-height: 36px; font-size: var(--text-base); }

/* ── Skipping the import (iPhone / no backup) ──────────────────────────────────────────────────
   ⚠ A QUIET BLOCK UNDER THE STEP, NOT A SECOND PRIMARY ACTION - importing is still the better
   outcome for anybody who can, and the page must not make skipping look recommended. */
.signup .skip { margin: 18px 0 0; padding: 14px 0 0; border-top: 1px solid var(--line); }
.signup .skip .skiphead { margin: 0 0 8px; font-size: var(--text-sm); font-weight: var(--weight-strong); }
.signup .skip .hint { margin-top: 8px; }
.signup .wstep.skipped .wdot { background: var(--bg); border-color: var(--live); color: var(--live); }

/* ── The pairing code ──────────────────────────────────────────────────────────────────────────
   ⛔ THESE ARE THE HARDEST EIGHT CHARACTERS IN THE PRODUCT. They are read off one screen, typed
   into another, under a deadline of about a minute, and a misread `0` for `O` costs the pairing -
   which cannot simply be retried, because each new code CANCELS the one before it. Large,
   monospaced and widely tracked is the whole job of this block. */
.signup .codeslab {
    display: flex; align-items: center; flex-wrap: wrap; gap: 12px;
    background: var(--brandsoft); border: 1px solid var(--brandline);
    border-radius: var(--radius-lg); padding: 16px 18px; margin: 0 0 6px;
}
.signup .codeslab code {
    flex: 1; min-width: 0; background: none; padding: 0; word-break: break-all;
    font-family: var(--font-mono);
    font-size: var(--text-xl); font-weight: var(--weight-strong); letter-spacing: .22em; color: var(--branddeep);
}
.signup .copied {
    min-height: 17px; margin: 0 0 14px; font-size: var(--text-sm); font-weight: 600; color: var(--live);
}
.signup .copied:empty { min-height: 0; margin: 0; }

/* ── Waiting ───────────────────────────────────────────────────────────────────────────────────
   ⛔ THIS SCREEN DELIBERATELY HAS NOTHING TO PRESS - a second ask CANCELS the code on its way, so
   the correct action is to do nothing. Motion is therefore the ONLY thing that can tell a visitor
   the product is still working, which makes these animations functional rather than decorative. */
.signup .alive { text-align: center; padding: 8px 0 2px; }
.signup .alive h1, .signup .alive h2 { margin-bottom: 4px; }
.signup .alive .lede { margin-bottom: 16px; }
.signup .ring {
    display: block; width: 44px; height: 44px; margin: 0 auto 16px; border-radius: 50%;
    border: 3px solid var(--brandline); border-top-color: var(--live);
    animation: wacspin 900ms linear infinite;
}
.signup .marquee {
    height: 4px; margin: 0 0 4px; border-radius: var(--radius-pill); background: var(--soft); overflow: hidden;
}
.signup .marquee span {
    display: block; width: 38%; height: 100%; border-radius: var(--radius-pill); background: var(--live);
    animation: wacslide 1.6s ease-in-out infinite;
}
@keyframes wacspin { to { transform: rotate(360deg); } }
@keyframes wacslide { from { margin-left: -40%; } to { margin-left: 102%; } }

/* ── Notices, wins and the file drop ───────────────────────────────────────────────────────────
   ⚠ A NOTICE IS VALIDATION, NEVER AN ERROR REPORT (constitution law 1). It is styled to look
   deliberate for exactly that reason: everything that lands here - a number already connected, a
   number that is not one, a service that is full - is the product working as designed. */
.signup .notice {
    display: flex; align-items: flex-start; gap: 10px; margin: 0 0 14px;
    padding: 11px 13px; border-radius: var(--radius-md); font-size: var(--text-sm); line-height: 1.45;
}
.signup .notice .nicon {
    flex: none; width: 18px; height: 18px; margin-top: 1px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: var(--text-xs); font-weight: var(--weight-strong); color: var(--bg);
}
.signup .notice.warn {
    background: var(--warnbg); color: var(--warnink); border: 1px solid var(--warnline);
}
.signup .notice.warn .nicon { background: var(--owed); }
.signup .notice.bad {
    background: var(--badbg); color: var(--badink); border: 1px solid var(--badline);
}
.signup .notice.bad .nicon { background: var(--badink); }
.signup .won {
    display: flex; align-items: center; gap: 8px; margin: 0 0 14px;
    font-size: var(--text-sm); font-weight: 600; color: var(--branddeep);
}
.signup .tick {
    flex: none; width: 20px; height: 20px; border-radius: 50%; background: var(--live);
    color: var(--surface); display: flex; align-items: center; justify-content: center; font-size: var(--text-xs);
}
.signup .finished { text-align: center; margin: 0 0 4px; }
.signup .finished .tick { width: 52px; height: 52px; margin: 4px auto 14px; font-size: var(--text-xl); }
.signup .finished h1, .signup .finished h2 { margin-bottom: 10px; }
.signup .finished + .lede { text-align: center; }
/* The finished card's three facts - now, where, next. A tinted panel so they read as one block of
   "what happens from here" rather than three more paragraphs of body text.
   ⛔ THE CLASS IS `facts`, NEVER `next`. The trail's unreached steps are `wstep next`, and a card
   rule for a `next` list matched them too - every future step rendered as a grey tinted box at
   390px (found in a browser 2026-09-24). `AppSignUp.test.ts` fails if that selector comes back. */
.signup .facts {
    list-style: none; margin: 4px 0 18px; padding: 14px 16px; border-radius: var(--radius-lg);
    background: var(--soft); border: 1px solid var(--line);
    font-size: var(--text-base); line-height: 1.5; color: var(--ink);
}
.signup .facts li { position: relative; padding-left: 22px; }
.signup .facts li + li { margin-top: 8px; }
.signup .facts li::before {
    content: ""; position: absolute; left: 4px; top: .55em;
    width: 7px; height: 7px; border-radius: 50%; background: var(--live);
}
.signup .drop {
    background: var(--soft); border: 1.5px dashed var(--line); border-radius: var(--radius-lg);
    padding: 18px; text-align: center;
}
.signup .drop input[type="file"] { font: inherit; font-size: var(--text-sm); max-width: 100%; }
/* ⚠ THE NATIVE CONTROL IS STYLED, NOT REPLACED. The label-and-hidden-input trick looks better and
   costs more than it is worth here: `wac-pick` is the element `AppMain` binds `change` to, and a
   visually hidden input is unusable the moment this stylesheet fails to load - on the one step
   that cannot be skipped. `::file-selector-button` degrades to the browser's own button. */
.signup .drop input[type="file"]::file-selector-button {
    font: inherit; font-size: var(--text-sm); font-weight: 600; margin-right: 10px; cursor: pointer;
    background: var(--bg); color: var(--ink); border: 1px solid var(--line);
    border-radius: var(--radius-md); padding: 8px 14px;
}
.signup .drop input[type="file"]::file-selector-button:hover { background: var(--soft); }
/* `AppMain.OnImportStep` writes here directly, so it must stay a plain text line - see `AskKey`. */
.signup .progress {
    margin: 10px 0 0; font-size: var(--text-sm); color: var(--dim); font-variant-numeric: tabular-nums;
}
.signup .progress:empty { display: none; }
.signup .vouch { margin: 16px 6px 0; text-align: center; font-size: var(--text-xs); color: var(--dim); }

@media (max-width: 480px) {
    .signup .panel { padding: 20px 18px 24px; border-radius: var(--radius-lg); }
    .signup .fieldrow { display: block; }
    .signup .fieldrow input { width: 100%; margin-bottom: 10px; }
    .signup .fieldrow .btn { width: 100%; }
    .signup .guide .btn, .signup .skip .btn { display: flex; width: 100%; }
    .signup .codeslab code { font-size: var(--text-xl); letter-spacing: .16em; }
}

/* ⚠ FOUR LABELS CANNOT SHARE A 360px LINE without breaking mid-word, and four dots with no words
   would leave nothing saying which step is which - which is exactly why the eyebrow states the
   step in words as well as drawing it. */
@media (max-width: 420px) {
    .signup .wname { display: none; }
    .signup .wiz { margin-bottom: 18px; }
}

/* ⚠ THE MARQUEE GOES FULL-WIDTH RATHER THAN STILL. Frozen at 38% it would read as a progress bar
   that has stopped, which is the one impression the waiting screen exists to prevent. */
@media (prefers-reduced-motion: reduce) {
    .signup .ring, .signup .marquee span, .signup .btn .spin, .signup .wstep.now .wdot::after {
        animation: none;
    }
    .signup .marquee span { width: 100%; }
}


/* ══ Tasks (2026-09-26): the Tasks tab and the Today strip ════════════════════════════════════
   ⚠ ONLY REACHED FOR AN INDEX WITH THE NEW FIELDS (`AppTaskRules.Enabled`); an old index still
   draws `.rows`/`.row` above. Same tokens as the rest - nothing here introduces a colour.
   ⚠ A REFUSAL UNDER THE SEARCH BOX (`.tsay`) IS INK, NEVER ORANGE: `AppTaskApi.NotYetWhy` and a lost
   connection are states, not faults. The only alarm colour is the Overdue chip.
   ⚠ CHIP CLASSES ARE `d-*` AND THE STRIP IS `.tasktoday`: a bare `.today` chip class picked up the
   strip's padding and border (measured in Chrome, 2026-09-26). */
.tasksview .tbar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; margin: 0 0 6px; }
.tasksview .tfind { flex: 1 1 260px; margin: 0; }
.tsay { margin: 4px 0 10px; font-size: var(--text-sm); color: var(--ink); min-height: 1.2em; }
.tsay:empty { min-height: 0; margin: 0; }

.tfilter { display: flex; gap: 6px; margin: 0 0 12px; }
.tf { font: inherit; font-size: var(--text-sm); color: var(--muted); background: var(--surface); cursor: pointer;
      border: 1px solid var(--line); border-radius: var(--radius-md); padding: 4px 12px; }
.tf i { font-style: normal; margin-left: 6px; }
.tf[aria-pressed="true"] { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-line); font-weight: var(--weight-strong); }
.tf:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

.tgroup { margin: 0 0 16px; }
.tgroup h2 { margin: 0 0 6px; font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--muted); }
.tgroup h2 i { font-style: normal; font-weight: var(--weight-strong); margin-left: 7px; padding: 0 6px; border-radius: var(--radius-sm);
               background: var(--line); color: var(--ink); }
.tgroup.g-overdue h2 { color: var(--badink); }
.tgroup.g-overdue h2 i { background: var(--badbg); color: var(--badink); }
/* "Older (N)" - a folded section (a <details>): closed until clicked, by keyboard too. */
.tgroup.tfold > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: baseline; gap: 6px;
                          border-radius: var(--radius-sm); padding: 2px 4px 2px 0; }
.tgroup.tfold > summary::-webkit-details-marker { display: none; }
/* The fold's chevron is our icon (`AppIcons.ChevronDown`), turned to point right while the section is shut. */
.tgroup.tfold > summary .wi-chevdown { color: var(--muted); transform: rotate(-90deg); transition: transform .12s ease; }
.tgroup.tfold[open] > summary .wi-chevdown { transform: none; }
.tgroup.tfold > summary h2 { margin: 0; }
.tgroup.tfold[open] > summary { margin: 0 0 6px; }
.tgroup.tfold > summary:hover h2, .tgroup.tfold > summary:focus-visible h2 { color: var(--ink); }

.tasks { list-style: none; margin: 0; padding: 0; background: var(--bg); border: 1px solid var(--line);
         border-radius: var(--radius-lg); }
.task { display: flex; align-items: flex-start; gap: 12px; padding: 10px 12px; border-top: 1px solid var(--line);
        position: relative; }
.task:first-child { border-top: 0; }
.task.tpending { opacity: .55; }
.task .tick {
    flex: none; width: 24px; height: 24px; margin-top: 1px; border-radius: 50%; padding: 0; cursor: pointer;
    border: 2px solid var(--dim); background: transparent; color: transparent; font-size: var(--text-sm); line-height: 20px;
    display: inline-flex; align-items: center; justify-content: center; text-decoration: none;
}
.task .tick:hover, .task .tick:focus-visible { border-color: var(--live); color: var(--live); }
.task .tick[aria-pressed="true"] { background: var(--live); border-color: var(--live); color: var(--surface); }
.task .tick:disabled, .task .tick[aria-disabled="true"] { cursor: default; }
.task .tick.treply { border-color: var(--acc); color: var(--acc); font-size: var(--text-base); }
.task .tick.tnone { border-style: dashed; opacity: .45; cursor: default; }
.task .tbody { flex: 1; min-width: 0; }
.task .tl { margin: 0; overflow-wrap: anywhere; }
.task .tl b { font-weight: 600; }
.task .tv { color: var(--ink); opacity: .85; }
.task.b-done .tl { text-decoration: line-through; color: var(--dim); }
.task .tm { margin: 3px 0 0; font-size: var(--text-sm); color: var(--dim); display: flex; flex-wrap: wrap; gap: 4px 10px;
            align-items: baseline; }
.task .tm .who { color: var(--ink); font-weight: 500; }
.task .due { border-radius: var(--radius-sm); padding: 0 6px; font-weight: 600; font-size: var(--text-xs); background: var(--soft);
             color: var(--ink); border: 1px solid var(--line); }
.task .due.d-overdue { background: var(--badbg); color: var(--badink); border-color: var(--badline); }
.task .due.d-today { background: var(--brandsoft); color: var(--branddeep); border-color: var(--brandline); }
.task .tmsg { color: var(--acc); text-decoration: none; white-space: nowrap; }
.task .tmsg:hover { text-decoration: underline; }
/* ⭐ THE WHOLE ROW IS ONE LINK (AppTaskView.OpenLink): the title's <a> is stretched over the row;
   the tick, the "..." menu and "↗ message" sit above it and stay their own targets. */
.task.topen { cursor: pointer; }
.task.topen:hover { background: var(--soft); }
.task a.trow { color: inherit; text-decoration: none; outline: none; }
.task a.trow::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.task a.trow:focus-visible::after { outline: 2px solid var(--acc); outline-offset: -2px; }
.task.topen .tick, .task.topen .tmenu, .task.topen .tmsg { position: relative; z-index: 2; }
.task.topen .tmenu[open] { z-index: 20; }
.tasktoday .tthead { color: inherit; text-decoration: none; }
.tasktoday .tthead:hover, .tasktoday .tthead:focus-visible { color: var(--acc); text-decoration: underline; }

/* The "..." menu - a <details>, so it works by keyboard and without script. */
.tmenu { flex: none; position: relative; }
.tmenu summary { list-style: none; cursor: pointer; padding: 0 8px; border-radius: var(--radius-md); color: var(--dim);
                 font-size: var(--text-md); line-height: 24px; }
.tmenu summary::-webkit-details-marker { display: none; }
/* Our own SVG icons (App/src/AppIcons.ts) - the tick, reply, go-to-message and menu glyphs, drawn in currentColor. */
svg.wi { display: inline-block; flex: none; vertical-align: -.15em; overflow: visible; }
.tmenu summary { display: inline-flex; align-items: center; height: 24px; }
.tmenu summary:hover, .tmenu[open] summary { background: var(--soft); color: var(--ink); }
.tpop { position: absolute; right: 0; top: 28px; z-index: 15; min-width: 270px; padding: 5px;
        background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-md);
        box-shadow: 0 8px 24px rgba(16, 20, 28, .16); display: flex; flex-direction: column; }
.tpop button { font: inherit; font-size: var(--text-sm); color: var(--ink); background: transparent; border: 0;
               text-align: left; padding: 7px 10px; border-radius: var(--radius-md); cursor: pointer; }
.tpop button:hover, .tpop button:focus-visible { background: var(--soft); }
.tpop button:focus-visible { outline-offset: -2px; }
.tpick { display: flex; gap: 6px; align-items: center; padding: 4px 4px 4px 10px; }
.tpick input { font: inherit; font-size: var(--text-sm); color: var(--ink); background: var(--bg); min-width: 0; flex: 1;
               border: 1px solid var(--line); border-radius: var(--radius-md); padding: 4px 6px; }
.tpick button { flex: none; background: var(--soft); }

/* The new-task row - the first row of the list, always there (no side form). */
.tnew { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 12px; padding: 8px 10px;
        background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-md); }
.tnew .tnewico { flex: none; color: var(--dim); }
.tnew input, .tnew select { font: inherit; font-size: var(--text-sm); color: var(--ink); background: var(--bg);
    border: 1px solid var(--line-strong); border-radius: var(--radius-md); padding: 5px 8px; min-width: 0; }
.tnew .tnewl { flex: 1 1 200px; }
.tnew select, .tnew input[type="date"] { flex: 0 1 auto; }
.tnew input:not(.tnewl):not([type="date"]) { flex: 1 1 120px; }
.tnew .btn { flex: none; }
/* ⚠ A date box's focus sits on its inner day/month fields and its calendar button (a Tab stop where the box no longer
   matches `:focus`, measured in Chrome) - so the ring is on `:focus` AND `:focus-within`. */
.tnew input:focus, .tnew input:focus-within, .tnew select:focus { outline: var(--focus-ring); outline-offset: 1px; border-color: var(--accent); }

.tempty { background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 14px 16px;
          font-size: var(--text-base); color: var(--ink); max-width: 64ch; }
.tempty p { margin: 0 0 8px; }
.tempty p:last-child { margin: 0; color: var(--dim); }

/* The Today strip on the chats screen. */
.tasktoday { margin: 0 0 14px; padding: 10px 12px 8px; border-radius: var(--radius-lg); background: var(--surface);
             border: 1px solid var(--line); border-left: 3px solid var(--accent); }
.tasktoday header { display: flex; align-items: baseline; gap: 8px; margin: 0 0 6px; }
.tasktoday h2 { margin: 0; font-size: var(--text-base); }
.tasktoday header i { font-style: normal; font-size: var(--text-xs); font-weight: var(--weight-strong); padding: 0 6px; border-radius: var(--radius-sm);
                      background: var(--accent-soft); color: var(--accent); border: 1px solid var(--accent-line); }
.tasktoday .seeall { margin-left: auto; font-size: var(--text-sm); color: var(--accent); text-decoration: none;
                      display: inline-flex; align-items: center; gap: 4px; }
.tasktoday .more a { display: inline-flex; align-items: center; gap: 4px; }
.tasktoday .tasks { border: 0; border-radius: 0; background: transparent; }
.tasktoday .task { padding: 7px 0; }
.tasktoday .task.topen { margin: 0 -8px; padding: 7px 8px; border-radius: var(--radius-md); }
.tasktoday .more { margin: 4px 0 0; font-size: var(--text-sm); }
.tasktoday .more a { color: var(--acc); text-decoration: none; }
.tasktoday .none { margin: 2px 0 4px; }

@media (max-width: 620px) {
    .tasksview .tbar { align-items: stretch; }
    .tnew .tnewl { flex-basis: calc(100% - 40px); }
    .tpop { min-width: 250px; max-width: calc(100vw - 48px); }
    .task { gap: 10px; padding: 10px; }
}

/* ── "Let <name> know?" - closing a task is two clicks (2026-09-26) ─────────────────────────────
   ⚠ THE CARD IS A SUGGESTION, NOT AN ALARM: brand-soft, ink text; a refusal inside it is ink too.
   ⚠ ON THE TASKS TAB IT STICKS UNDER THE TOP: a done row moves to "Done" (often far below) and leaves
   the Today strip altogether, so a card drawn on the row would jump away from the owner's finger. */
.tnotify:empty { display: none; }
.tnotify { display: flex; flex-direction: column; gap: 10px; margin: 0 0 14px; }
body:not(.detail) .tnotify { position: sticky; top: 8px; z-index: 5; }
.tnote { background: var(--bg); border: 1px solid var(--brandline); border-left: 3px solid var(--ai);
         border-radius: var(--radius-lg); padding: 10px 12px; }
.tnhead { margin: 0 0 8px; font-size: var(--text-base); line-height: 1.4; color: var(--ink); }
.tnhead b { font-weight: var(--weight-strong); }
.tnwhat { color: var(--dim); overflow-wrap: anywhere; }
.tnwait { display: flex; align-items: center; gap: 10px; margin: 0 0 8px; font-size: var(--text-sm); color: var(--dim); }
.tnbubble {
    margin-left: auto; width: fit-content; max-width: 100%; padding: 8px 12px; font-size: var(--text-base);
    line-height: 1.45; color: var(--ink); background: var(--mebubble); border: 1px solid var(--accent-line); border-radius: var(--radius-md);
    white-space: pre-wrap; overflow-wrap: anywhere;
}
.tnacts { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin: 9px 0 0; }
.tnwhy { font-size: var(--text-sm); line-height: 1.45; color: var(--ink); margin: 6px 0 0; }
.tnsent { font-size: var(--text-sm); line-height: 1.45; color: var(--dim); margin: 7px 0 0; text-align: right; }
/* One AI area (owner, 2026-09-26): on the contact page the card is the newest entry of the AI panel's
   conversation - under the log, above the chips - drawn like a draft card there. The two scroll
   regions need a visible boundary: a clipped older draft must not appear covered by the new card. */
.assist .tnotify { flex: 0 1 auto; min-height: 0; margin: 0; padding: var(--space-3) 14px 12px; gap: 10px; max-height: 55%; overflow-y: auto;
                   border-top: 1px solid var(--line); background: var(--surface); }
/* ⛔ THE CARD KEEPS ITS BUTTONS (2026-10-13): the log's basis was half the panel (`.alog`, 50%), so when the panel was
   too short for everything BOTH regions shrank in proportion - on a phone's sheet the card lost its last 30px (Skip /
   Edit / Send cut by the chip row) while the log above it stood two thirds empty. While a card is up the log's basis is
   nothing: it takes what is left and the card's region gives way only when the card alone cannot fit (and then it
   scrolls). On the sheet the region may take all the room between the header and the chips, and its buttons are
   full finger targets. */
.assist:has(.tnotify:not(:empty)) .alog { flex-basis: 0; }
@media (max-width: 1099px) {
    .assist .tnotify { max-height: none; }
    .assist .tnacts .btn { min-height: 44px; }
}
.assist .tnote { flex: none; box-shadow: none; border: 1px solid var(--brandline); border-radius: var(--radius-lg);
                 background: var(--soft); padding: 10px; }
.assist .tnote.tnquiet { border-left: 3px solid var(--live); background: var(--bg); }
.assist .tnhead { font-size: var(--text-sm); }
@media (max-width: 620px) {
    .tnote { padding: 9px 10px; }
    .tnacts .btn { flex: 1 1 auto; }
}
/* "No message needed" - one quiet ink line, no send. */
.tnote.tnquiet { border-left-color: var(--live); box-shadow: none; padding: 7px 10px; }
.tntold { margin: 0; font-size: var(--text-sm); line-height: 1.45; color: var(--ink); }
.tnok { color: var(--success); margin-right: 6px; vertical-align: -3px; }
.tnx.btn { float: right; margin: -4px -4px 0 4px; color: var(--muted); }

/* ══ Overview (2026-09-27, `AppOverviewView`): most in touch, gone quiet, replies owed ═══════════════
   Exact counts from the service (`index.json` `act`), no AI. ONE series, so no legend - the heading names
   it; thin bars from one baseline with a 4px round end (dataviz skill), the number in INK beside each bar,
   never in the bar's colour. `--viz1` is the validated categorical slot 1, with its own dark step. */
.ov { max-width: 720px; }
.ovsec { background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 12px 14px; margin: 0 0 14px; }
.ovhead { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 6px 12px; margin: 0 0 8px; }
.ovhead h2 { margin: 0; font-size: var(--text-md); font-weight: var(--weight-strong); color: var(--text); }
.ovper { font-size: var(--text-sm); color: var(--dim); }
.ovbars, .ovlist { list-style: none; margin: 0; padding: 0; }
.ovbars a { display: grid; grid-template-columns: minmax(0, 11em) minmax(0, 1fr) auto; align-items: center; gap: 10px;
            padding: 5px 4px; border-radius: var(--radius-md); color: var(--ink); text-decoration: none; }
.ovbars a:hover, .ovlist a:hover { background: var(--soft); }
.ovname { display: flex; align-items: center; min-width: 0; font-size: var(--text-base); }
.ovname > .av { width: 24px; height: 24px; font-size: var(--text-xs); margin-right: 6px; flex: none; }
.ovt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A named contact with no picture has no disc (`AppAvatarRules.Small`): keep the names in one column. */
.ovname > .ovt:first-child { margin-left: 24px; }
.ovbar { display: block; width: 100%; height: 8px; overflow: visible; }
.ovfill { fill: var(--viz1); }
.ovnum { font-size: var(--text-sm); font-variant-numeric: tabular-nums; color: var(--ink); text-align: right; min-width: 2.5em; }
.ovlist a { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; padding: 6px 4px;
            border-radius: var(--radius-md); color: var(--ink); text-decoration: none; }
.ovwhy { font-size: var(--text-sm); color: var(--dim); }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
@media (max-width: 480px) {
    .ovsec { padding: 10px 10px; }
    .ovbars a { grid-template-columns: minmax(0, 8.5em) minmax(0, 1fr) auto; gap: 8px; }
}

/* ── "Ask about everyone" under the search box (`AppAskAll`, 2026-09-27) ────────────────────
   The same log, chips and Ask box as the chat's AI panel, inline on the main screen (never a fixed
   column). Links in its answers open the named person's chat (`a.alink`). */
.askall { margin: -4px 0 12px; max-width: 560px; }
.askallbtn.btn { gap: 2px; background: var(--accent-soft); border-color: var(--accent-line); }
.askallbtn.btn[aria-expanded="true"] { border-color: var(--accent); }
.askallpanel {
    display: flex; flex-direction: column; margin-top: 8px; overflow: hidden;
    background: var(--bg); border: 1px solid var(--ailine); border-radius: var(--radius-lg);
}
.askallpanel[hidden] { display: none; }
.askallpanel .alog { max-height: min(60vh, 520px); min-height: 90px; }
/* An empty log (no question yet - its explanation is the heading's tooltip) takes no room. */
.askallpanel .alog:empty { display: none; }
.aa .alink {
    display: inline-block; margin: 0 2px; padding: 0 7px; font-size: var(--text-xs); font-weight: var(--weight-strong); line-height: 1.6;
    color: var(--aiink); text-decoration: none; background: var(--aisoft); border: 1px solid var(--ailine);
    border-radius: var(--radius-sm); white-space: nowrap; vertical-align: 1px;
}
/* A drawn chevron (two borders turned), not a typed "›". */
.aa .alink::after { content: ""; display: inline-block; width: 5px; height: 5px; margin: 0 1px 1px 5px;
                    border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(-45deg); }
.aa .alink:hover { border-color: var(--ai); }
.aa .alink:focus-visible { outline: 2px solid var(--ai); outline-offset: 1px; }
@media (max-width: 560px) { .askall { max-width: none; } }

/* ══ Translation (2026-09-27, `AppTranslateView`): the state chip in the chat header, the one quiet line under the
   header, and the settings themselves - since 2026-10-13 in the "Chat settings" dialog's Translation section ══
   ⚠ Shown only where `/translate` says translation is available - a dormant service draws none of this.
   ⚠ The chip is drawn only while the chat IS translated ("EN ⇄ ID"); it opens the dialog and changes nothing.
   ⚠ The quiet line is INK (a suggestion, the monthly cap); only an unreadable settings file uses the alarm colour.
   ⛔ No inline style anywhere (the CSP): hiding is the `hidden` attribute. */
.trchipbox { display: inline-flex; flex: none; }
.trchipbox:empty { display: none; }
.trchip {
    display: inline-flex; align-items: center; gap: 6px; font: inherit; font-size: var(--text-sm); font-weight: var(--weight-strong);
    color: var(--muted); background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-md);
    padding: 3px 10px; min-height: 30px; cursor: pointer; white-space: nowrap;
}
.trchip.on { color: var(--aiink); background: var(--aisoft); border-color: var(--ailine); }
.trchip:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.trchip svg { flex: none; }
.trline { flex: none; margin: 0 0 8px; font-size: var(--text-sm); color: var(--ink); line-height: 1.5; }
.trline.bad { color: var(--badink); }
.trline[hidden] { display: none; }
.trgo.btn[aria-disabled="true"] { cursor: progress; }
.trrow { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; }
.trlangs { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 0 0 12px; }
.trsel { display: flex; flex-direction: column; gap: 3px; font-size: var(--text-sm); color: var(--dim); min-width: 0; }
.trsel select { font: inherit; font-size: var(--text-base); color: var(--ink); background: var(--bg); border: 1px solid var(--line);
                border-radius: var(--radius-md); padding: 5px 8px; max-width: 100%; }
.trbtns { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 4px 0 0; }
.trnote { margin: 6px 0 0; font-size: var(--text-sm); color: var(--ink); }
.trnote.bad { color: var(--badink); }
.trnote:empty { display: none; }

/* === wac-typing: "typing…" on a chat card (AppLiveTyping.ts, 2026-09-30) - wac-ds may restyle, keep the class names === */
.card.wac-typing-on .r:not(.wac-typing) { display: none; }
.card .r.wac-typing { color: var(--accent); font-style: italic; }
/* === end wac-typing === */

/* === wac-keys: keyboard shortcuts help + the Ctrl/Cmd+K palette (AppKeys.ts, AppPalette.ts, 2026-09-30) - tokens only === */
.wac-pal, .wac-keydlg {
    border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); color: var(--text);
    padding: 0; width: min(560px, calc(100vw - 32px)); max-height: min(70vh, 560px); box-shadow: 0 16px 48px rgba(16, 20, 28, .28);
    font-family: var(--font-ui); font-size: var(--text-base);
}
.wac-pal { margin: 12vh auto auto; overflow: hidden; }
.wac-pal[open] { display: flex; flex-direction: column; }
.wac-pal::backdrop, .wac-keydlg::backdrop { background: rgba(16, 20, 28, .45); }
.wac-pal .palbox { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-3) var(--space-4);
                   border-bottom: 1px solid var(--line); color: var(--muted); }
.wac-pal .palbox input { flex: 1; min-width: 0; font: inherit; font-size: var(--text-md); color: var(--text); background: transparent;
                         border: 0; outline: none; padding: var(--space-1) 0; }
.wac-pal .palbox:focus-within { box-shadow: inset 0 -2px 0 var(--accent); }
.wac-pal .pallist { list-style: none; margin: 0; padding: var(--space-1); overflow-y: auto; flex: 1 1 auto; min-height: 0; }
.wac-pal .palopt { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-2) var(--space-3);
                   border-radius: var(--radius-md); cursor: pointer; }
.wac-pal .palopt.on { background: var(--accent-soft); outline: 1px solid var(--accent-line); }
.wac-pal .palic { flex: none; display: inline-flex; color: var(--muted); }
.wac-pal .palopt.on .palic { color: var(--accent); }
.wac-pal .paltx { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.wac-pal .palt { font-weight: var(--weight-strong); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wac-pal .pals { font-size: var(--text-sm); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wac-pal .palk { flex: none; font-size: var(--text-xs); color: var(--muted); }
.wac-pal .palstatus { margin: 0; padding: var(--space-1) var(--space-4); font-size: var(--text-xs); color: var(--muted); }
.wac-pal .palstatus:empty { display: none; }
.wac-pal .palhint { margin: 0; padding: var(--space-2) var(--space-4); border-top: 1px solid var(--line); font-size: var(--text-xs);
                    color: var(--muted); }
.wac-pal kbd, .wac-keydlg kbd {
    display: inline-flex; align-items: center; justify-content: center; min-width: 20px; height: 20px; padding: 0 5px; margin: 0 2px;
    font: inherit; font-size: var(--text-xs); color: var(--text); background: var(--page); border: 1px solid var(--line);
    border-bottom-width: 2px; border-radius: var(--radius-sm);
}
.wac-keydlg { padding: var(--space-4) var(--space-5) var(--space-5); overflow-y: auto; }
.wac-keydlg .khead { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin: 0 0 var(--space-3); }
.wac-keydlg h2 { margin: 0; font-size: var(--text-lg); font-weight: var(--weight-strong); }
.wac-keydlg .keylist { margin: 0; }
.wac-keydlg .krow { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-2) 0; border-bottom: 1px solid var(--line); }
.wac-keydlg .krow dt { flex: none; width: 140px; margin: 0; }
.wac-keydlg .krow dd { margin: 0; color: var(--text); }
.wac-keydlg .kthen { font-size: var(--text-xs); color: var(--muted); margin: 0 2px; }
.wac-keydlg .knote { margin: var(--space-3) 0 0; font-size: var(--text-sm); color: var(--muted); }
@media (max-width: 560px) { .wac-pal { margin-top: var(--space-4); } .wac-keydlg .krow dt { width: 110px; } }
/* === end wac-keys === */

/* === wac-shell: top bar, chat list rows, two-line chat header, menus, skeletons (2026-09-30, design items 7/8/9/11a) ===
   AppTopBar/AppTopBarRun, AppMenu, AppChatHead, AppSkeleton. Tokens only - no colour is introduced here. */
.bar { position: relative; z-index: 30; }
.topline { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; max-width: 1440px; margin: 0 auto; }
.topline .brand { flex: none; }
.topsearch { flex: 1 1 320px; min-width: 0; max-width: 560px; }
.topsearch .find { margin: 0; }
.topsearch .find input { max-width: none; width: 100%; background: var(--bg); color: var(--ink); }
/* The count stays for a screen reader (aria-live); in the bar it would take a second line for every chat list. */
.topsearch .found { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; margin: 0; }
.topsearch[hidden] { display: none; }
.topright { display: flex; align-items: center; gap: 4px; margin-left: auto; flex: none; }
.topstatus { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-xs); color: var(--muted);
             padding: 4px 8px; border-radius: var(--radius-sm); white-space: nowrap; }
.topstatus[hidden] { display: none; }
.topstatus:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.topstatus .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--quiet); flex: none; }
.topstatus.st-success .dot { background: var(--success); }
.topstatus.st-warning .dot { background: var(--warning); }
.topmenu { position: relative; }
.bar .topbtn { color: var(--muted); }
.bar .topbtn:not(:disabled):hover, .bar .topbtn[aria-expanded="true"] { background: var(--accent-soft); color: var(--accent); }
.menupop { position: absolute; right: 0; top: calc(100% + 6px); z-index: 40; min-width: 220px; max-width: min(320px, calc(100vw - 32px));
           background: var(--surface); color: var(--ink); border: 1px solid var(--line); border-radius: var(--radius-md);
           box-shadow: 0 8px 24px rgba(16, 20, 28, .16); padding: 6px; font-size: var(--text-sm); }
.menupop[hidden] { display: none; }
.menuhead { margin: 4px 8px 6px; font-size: var(--text-xs); font-weight: var(--weight-strong); color: var(--muted); }
.menuitem { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 36px; padding: 6px 10px; border: 0;
            border-radius: var(--radius-sm); background: transparent; color: var(--ink); font: inherit; text-align: left; cursor: pointer; }
.menuitem:hover, .menuitem:focus-visible { background: var(--accent-soft); }
.menuitem:focus-visible { outline-offset: -2px; }
.menuitem[aria-checked="true"], .menuitem[aria-pressed="true"] { color: var(--accent); font-weight: var(--weight-strong); }
.menuitem svg { flex: none; }
.menuitem[disabled] { cursor: progress; opacity: .6; }
@media (max-width: 799px) {
    /* On a phone the search takes the bar's second line, full width. */
    .topsearch { order: 3; flex-basis: 100%; max-width: none; }
    .topstatus .stxt { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
}

/* ── The chat list as rows (the inbox): the SAME li.card markup, laid out as one list ── */
.cards.asrows { display: block; background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden; }
.asrows .card { border: 0; border-radius: 0; border-left: 3px solid transparent; border-top: 1px solid var(--line); }
.asrows .card:first-child { border-top: 0; }
.asrows .card.s-owed { border-left-color: var(--owed); }
.asrows .card a { grid-template-columns: 44px minmax(0, 1fr) auto; column-gap: 12px; row-gap: 2px; padding: 9px 14px; align-items: center; }
.asrows .card .av { grid-row: 1 / 3; grid-column: 1; }
.asrows .card .t { grid-row: 1; grid-column: 2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.asrows .card .m { grid-row: 1; grid-column: 3; margin-top: 0; text-align: right; white-space: nowrap; }
.asrows .card .r { grid-row: 2; grid-column: 2 / 4; margin-top: 0; }
@media (max-width: 520px) {
    /* ⚠ On a phone the name gets the whole first line: beside "Waiting on you · 9 days · 1 to do" it had ~80 px, so a
       number-only chat ("+62 812-3345-7887") or any longer name was cut to a few letters. The meta shares line 2 with
       the preview, which is the part that may be cut. */
    .asrows .card .t { grid-column: 2 / 4; }
    .asrows .card .m { grid-row: 2; font-size: var(--text-xs); align-self: start; }
    .asrows .card .r { grid-column: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

/* ── The chat header on two lines (AppChatHead) ── */
.chathead { display: flex; align-items: center; gap: 10px; min-width: 0; }
.chathead .chback { flex: none; margin-left: -8px; }
.chathead .av { width: 40px; height: 40px; font-size: var(--text-base); }
.chid { flex: 1 1 auto; min-width: 0; }
.chname { margin: 0; font-size: var(--text-md); font-weight: var(--weight-strong); line-height: 1.25;
          overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chsub { margin: 1px 0 0; font-size: var(--text-xs); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chacts { display: flex; align-items: center; gap: 4px 8px; flex: none; max-width: 55%; justify-content: flex-end; flex-wrap: wrap; }
/* ⛔ THE EXPORT NOTE IS A LINE OF ITS OWN UNDER THE HEADER'S FIELDS (2026-10-13), never beside them: "Preparing the
   file… 3 s", "Saved "<name> - WhatsApp chat.csv" - 3 messages." and a refusal sentence are the header's LAST child
   (`AppChatHead.Html`), the full width, in the header's last row at every width. Inside `.chacts` it took its width from
   the property row (a narrow wrapping column at 1280) and pushed "⋯" onto its own line at 390. Empty = no room at all. */
.chathead { flex-wrap: wrap; }
.chathead > .xportnote { order: 9; flex: 0 0 100%; grid-column: 1 / -1; box-sizing: border-box; min-width: 0; margin: 0;
                         padding: var(--space-1) 0 0; border-top: 1px solid var(--line); text-align: left; }
/* Phone: the More button (and the translation chip, while the chat is translated) sits beside the identity. */
@media (max-width: 480px) {
    .chathead { display: grid; grid-template-columns: 32px 40px minmax(0, 1fr) auto; gap: 6px 10px; }
    .chacts { grid-column: 4; grid-row: 1; width: auto; max-width: none; }
}
.chmenu .topbtn { color: var(--muted); }
.chmenu .topbtn:not(:disabled):hover, .chmenu .topbtn[aria-expanded="true"] { background: var(--accent-soft); color: var(--accent); }
.chatviews { display: flex; flex-wrap: wrap; gap: 4px; margin: 6px 0; }
.chatviews button { display: inline-flex; align-items: center; gap: 6px; min-width: 44px; justify-content: center; }
.chatviews [aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); }

/* ── Loading placeholders (AppSkeleton) ── */
.skelrows { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--bg); }
.skelrow { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-top: 1px solid var(--line); }
.skelrow:first-child { border-top: 0; }
.skellines { flex: 1; display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.skel { display: block; background: var(--soft); border-radius: var(--radius-sm); animation: wacskel 1.4s ease-in-out infinite; }
.skeldisc { width: 44px; height: 44px; border-radius: 50%; flex: none; }
.skelline { height: 12px; }
.skelline.short { height: 10px; }
.skelline.w1 { width: 46%; } .skelline.w2 { width: 62%; } .skelline.w3 { width: 34%; } .skelline.w4 { width: 72%; }
.skelchat { flex: 1; min-height: 0; }
.skelbubbles { display: flex; flex-direction: column; gap: 10px; padding: 16px; border: 1px solid var(--line);
               border-radius: var(--radius-lg); background: var(--bg); height: 100%; box-sizing: border-box; }
.skelbubble { height: 34px; border-radius: var(--radius-md); }
.skelbubble.me { align-self: flex-end; background: var(--mebubble); box-shadow: inset 0 0 0 1px var(--accent-line); }
.skelbubble.w1 { width: 48%; } .skelbubble.w2 { width: 64%; } .skelbubble.w3 { width: 38%; } .skelbubble.w4 { width: 56%; }
@keyframes wacskel { 0%, 100% { opacity: 1; } 50% { opacity: .5; } }
@media (prefers-reduced-motion: reduce) { .skel { animation: none; } }
/* === end wac-shell === */

/* === wac-3col: the three-pane shell on a wide screen (2026-09-30, design item 12 "Yes, 3 columns"; AppLayout) ===
   >= 1100px (THE one breakpoint - `AppLayout.WideMin`, pinned by AppThreeCol.test.ts) once the archive is loaded: the chat
   list on the left, the conversation (or "Choose a chat", or Tasks / Overview / Revenue / Sent) in the center, the AI panel
   the center's own 360px right column. Every rule hangs off `html.wac-three` (set only while the panes are on the page), so
   below the breakpoint - and on the side routes (sign-in, leaving ...) - nothing here applies. Tokens only. */
@media (min-width: 1100px) {
    /* ⚠ THE PAGE ITSELF NEVER SCROLLS: each pane scrolls on its own (the list, the center's view, the frame). */
    html.wac-three body { height: 100vh; height: 100dvh; overflow: hidden; display: flex; flex-direction: column; }
    html.wac-three .bar { flex: none; padding: 10px 16px; }
    html.wac-three .topline { max-width: none; }
    html.wac-three main, html.wac-three body.detail main { flex: 1; min-height: 0; width: 100%; max-width: none; margin: 0; padding: 0;
                                                          display: flex; flex-direction: row; }
    /* The footer's links (Import a backup, Close my account) stay reachable: one quiet line under the panes. */
    html.wac-three .foot, html.wac-three body.detail .foot { display: block; flex: none; max-width: none; margin: 0;
        padding: var(--space-1) var(--space-4); border-top: 1px solid var(--line); background: var(--surface); }
    .threecol { flex: 1; min-width: 0; min-height: 0; display: flex; }
    .paneleft { flex: 0 0 clamp(300px, 26vw, 380px); min-width: 0; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
                padding: var(--space-3) var(--space-3) var(--space-4); background: var(--surface); border-right: 1px solid var(--line); }
    .panecenter { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow-y: auto;
                  overscroll-behavior: contain; padding: var(--space-3) var(--space-4); }
    /* An open chat: the frame scrolls, never the pane (one scrollbar per job). */
    html.wac-three body.detail .panecenter { overflow: hidden; padding-bottom: var(--space-3); }
    .panecenter > .contact, .panecenter > .skelchat { flex: 1; min-height: 0; }
    .panecenter > .chathead, .panecenter > .relink { flex: none; }
    /* A list view in the center reads at the width it always had.
       ⚠ NOT the view's screen-reader heading (`h1.sr`, audit 2026-10-12): this rule outweighs `.sr`, so the unseen 1px
       heading became 980px wide where it stood and pushed the whole page 49px sideways on Tasks, Overview, Revenue and Sent. */
    html.wac-three body:not(.detail) .panecenter > *:not(.sr) { width: 100%; max-width: 980px; margin-left: auto; margin-right: auto; flex: none; }
    /* The list is always beside the chat, so the header's "All chats" arrow would only repeat it. */
    html.wac-three .chback { display: none; }
    /* ── The left pane: the view tabs, the rows (one column), the message hits ── */
    .paneleft .cards { grid-template-columns: minmax(0, 1fr); }
    .paneleft .asrows .card a { padding: 9px 12px; column-gap: 10px; }
    /* ⚠ The pane is phone-narrow, so a row is laid out as on a phone: the name gets the whole first line (beside the
       meta it was cut to "Alic…"), the meta shares line 2 with the preview, which is the part that may be cut. */
    .paneleft .asrows .card .t { grid-column: 2 / 4; }
    .paneleft .asrows .card .m { grid-row: 2; font-size: var(--text-xs); align-self: start; }
    .paneleft .asrows .card .r { grid-column: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .paneleft .card a[aria-current="page"] { background: var(--accent-soft); }
    .paneleft .card:has(> a[aria-current="page"]) { border-left-color: var(--accent); }
    .paneleft .card a[aria-current="page"] .t { color: var(--accent); }
    .paneleft .msghits { margin: var(--space-4) 0 0; }
    .paneleft .none { padding: 0 var(--space-1); }
    /* ── The center with no chat open: calm, the keyboard hint, then the Today strip and "Ask about everyone" ── */
    .choose { text-align: center; padding: var(--space-6) var(--space-4) var(--space-5); color: var(--muted); }
    .choose h1 { margin: 0 0 var(--space-2); font-size: var(--text-lg); font-weight: var(--weight-strong); color: var(--text); }
    .choosehint { margin: 0 auto; max-width: 46ch; font-size: var(--text-sm); line-height: 1.8; }
    .choosehint kbd { display: inline-flex; align-items: center; justify-content: center; min-width: 20px; height: 20px; padding: 0 5px;
                      margin: 0 2px; font: inherit; font-size: var(--text-xs); color: var(--text); background: var(--page);
                      border: 1px solid var(--line); border-bottom-width: 2px; border-radius: var(--radius-sm); vertical-align: middle; }
    .choosehint kbd svg { display: block; }
    .panecenter .askall { max-width: none; margin-top: 0; }
}
/* ⚠ CROSSED BELOW THE BREAKPOINT WITH A CHAT OPEN (the chat is not redrawn - `AppMain.OnWideChange`): the same markup
   laid out as the single column it always was - the list pane hidden, the panes' boxes out of the way. */
@media (max-width: 1099px) {
    .paneleft { display: none; }
    .threecol, .panecenter { display: contents; }
}
/* === end wac-3col === */

/* === wac-standing: the account warning under the top bar (AppStanding.ts / AppStandingRun.ts, 2026-10-01) - tokens only ===
   One band right after `<header class="bar">`, on every view and width. `flex: none` because the body is a column in the
   chat view and in the three-pane shell (the panes keep the rest). Warning tint for a limit or a refused app version,
   danger tint when nothing is sent or received (banned, locked, temporarily blocked). The sentence is body text (--text). */
.standing { flex: none; padding: var(--space-2) var(--space-4); background: var(--warning-soft); color: var(--text);
            border-bottom: 1px solid var(--warning-line); font-size: var(--text-sm); line-height: 1.45; }
.standing[hidden] { display: none; }
.standing .stin { display: flex; align-items: flex-start; gap: var(--space-2); max-width: 1440px; margin: 0 auto; }
.standing .stico { flex: none; display: inline-flex; margin-top: 1px; color: var(--warning-ink); }
.standing .stbody { margin: 0; min-width: 0; }
.standing .sttitle { font-weight: var(--weight-strong); color: var(--warning-ink); }
.standing.st-danger { background: var(--danger-soft); border-bottom-color: var(--danger-line); }
.standing.st-danger .stico, .standing.st-danger .sttitle { color: var(--danger); }
html.wac-three .standing .stin { max-width: none; }
/* === end wac-standing === */

/* === wac-wastate: the top bar's line tells the truth about WhatsApp (AppWaLink.ts / AppTopBar.ts, 2026-10-01) - tokens only ===
   The line's words are `--muted` on the bar's surface (a measured pair) in every state. A state that needs attention
   (`st-warning`: reconnecting, not running, service not reachable; `st-danger`: not linked, refused by WhatsApp) swaps
   the dot for the warning icon - a SHAPE, so it does not rest on a small coloured dot alone, and it is
   what a phone shows (below 800px the words are for a screen reader only, as before). `st-danger` also underlines the
   words, so it differs from a warning without colour. */
.topstatus .sico { display: none; flex: none; color: var(--warning); }
.topstatus.st-danger .sico { color: var(--danger); }
.topstatus.st-danger .stxt { color: var(--danger); }
.topstatus .sico svg { display: block; }
.topstatus.st-warning .dot, .topstatus.st-danger .dot { display: none; }
.topstatus.st-warning .sico, .topstatus.st-danger .sico { display: inline-flex; }
.topstatus.st-danger .stxt { font-weight: var(--weight-strong); text-decoration: underline; text-underline-offset: 3px; }
/* ⭐ ON A PHONE A TAP SAYS IT IN WORDS (verifier 2026-10-01): below 800px the line is a dot or a triangle, and a touch
   screen has no tooltip - "something is wrong" with no way to learn WHAT. The line is focusable (tabindex 0), so a tap
   (or Tab) shows its words in a small bubble under it until the next tap elsewhere - text on the surface, edged. */
@media (max-width: 799px) {
    .topstatus { position: relative; }
    .topstatus:focus .stxt {
        position: absolute; top: calc(100% + 6px); right: 0; z-index: 40; width: auto; height: auto; overflow: visible; clip: auto;
        padding: 6px 10px; border-radius: var(--radius-md); background: var(--surface); color: var(--text);
        border: 1px solid var(--line-strong); box-shadow: 0 8px 24px rgba(16, 20, 28, .16);
    }
}
/* === end wac-wastate === */

/* === wac-signup: the sign-up and account screens brought onto the design system (2026-10-01) - tokens only ===
   Every rule here corrects or adds to the `.signup` rules above (it comes later, so it wins at equal weight). What it does:
   the type scale and radii are the tokens' (the block above carried 14.5 / 13.5 / 12.5 / 11.5px and 16 / 14 / 10px);
   the accent is the colour of what is happening NOW (the current step, the waiting ring and bar, links), success green is
   only what is DONE; a refusal sits under its field with one gap; the spinner takes the button's own text colour (it was
   white on the dark theme's light-blue button); and `info` is a notice about OUR end, in ink. */
.signup .panel { border-radius: var(--radius-lg); }
.signup h1[tabindex="-1"]:focus:not(:focus-visible), .signup h2[tabindex="-1"]:focus:not(:focus-visible) { outline: none; }
.signup h1[tabindex="-1"]:focus-visible, .signup h2[tabindex="-1"]:focus-visible {
    outline: var(--focus-ring); outline-offset: 4px; border-radius: var(--radius-sm);
}
.signup a:not(.btn) { color: var(--accent); }
.signup a:not(.btn):focus-visible, .signup summary:focus-visible { outline: var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }
.signup .lede, .signup .guide li, .signup .facts, .signup .kept, .signup .plan, .signup .target { font-size: var(--text-base); }
.signup .hint, .signup .aside, .signup .frozen, .signup .progress, .signup .copied, .signup .notice,
.signup .won, .signup .steps, .signup .flabel { font-size: var(--text-sm); }
.signup .vouch, .signup .wname, .signup code { font-size: var(--text-xs); }
.signup .codeslab code { font-size: var(--text-xl); }
.signup .fieldrow input { border-radius: var(--radius-md); }
.signup .fieldrow input:focus { box-shadow: 0 0 0 3px var(--accent-line); }
.signup .codeslab, .signup .facts, .signup .drop { border-radius: var(--radius-lg); }
/* The trail: done = success, now = accent. */
.signup .wstep.now .wdot { border-color: var(--accent); color: var(--accent); }
.signup .wstep.now .wdot::after { border-color: var(--accent); }
/* Waiting: the ring, the bar and the count are "happening now". The count ticks every second (`AppMain.OnWaitTick`). */
.signup .ring { border-color: var(--accent-line); border-top-color: var(--accent); }
.signup .marquee span { background: var(--accent); }
.signup .waited { margin: var(--space-2) 0 0; font-size: var(--text-sm); color: var(--muted); font-variant-numeric: tabular-nums; }
/* The pairing code: ink on the accent tint - the largest contrast the palette has, in both themes. */
.signup .codeslab, .relink .codeslab { background: var(--accent-soft); border-color: var(--accent-line); }
.signup .codeslab code, .relink .codeslab code { color: var(--text); }
.signup .codeslab { margin-bottom: var(--space-3); }
.signup .copied:not(:empty) { margin: 0 0 var(--space-3); }
/* Labels: real `<label>`s since this pass. */
.signup .flabel { margin: 0 0 6px; }
.signup .guide .flabel { margin-top: var(--space-3); }
/* A refusal under its field: one gap above, one below, whatever follows. The icon is `AppIcons.Warning`. */
.signup .notice { margin: var(--space-3) 0; border-radius: var(--radius-md); }
.signup .notice:last-child, .signup .guide li > .notice:last-child, .signup .guide li > .inkwhy:last-child { margin-bottom: 0; }
.signup .notice .nicon { width: auto; height: auto; margin-top: 1px; border-radius: 0; background: none; }
.signup .notice.bad .nicon { background: none; color: var(--danger); }
.signup .notice.warn .nicon { background: none; color: var(--warning); }
.signup .notice.info { background: var(--page); color: var(--text); border: 1px solid var(--line); }
.signup .notice.info .nicon { color: var(--muted); }
.signup .notice + .fieldrow { margin-top: var(--space-3); }
.signup .inkwhy { margin: var(--space-3) 0; border-radius: var(--radius-md); }
/* One spinner, in the colour of the text beside it - in a button and on a progress line. */
.signup .spin, .relink .spin {
    display: inline-block; flex: none; width: 14px; height: 14px; border-radius: 50%; vertical-align: -2px; margin-right: var(--space-2);
    border: 2px solid currentColor; border-top-color: transparent; animation: wacspin 800ms linear infinite;
}
.signup .btn .spin, .relink .btn .spin { margin-right: 0; border: 2px solid currentColor; border-top-color: transparent; }
/* "Dropbox says this app has reached its user limit?" - folded, opened by a tap or Enter. */
.signup details.more { margin: var(--space-2) 0 0; font-size: var(--text-sm); }
/* ⚠ `list-item`, so the browser draws its own disclosure triangle: as an inline block it read as a link that went nowhere. */
.signup details.more summary { display: list-item; padding: var(--space-1) 0; cursor: pointer; color: var(--accent); }
/* The signed-out card (`AppAccountViews.SignedOut`): the sentence in ink at reading size, the two ways in under it. */
.signup .signedout { margin: 0 0 var(--space-4); max-width: none; font-size: var(--text-md); line-height: 1.5; color: var(--text); }
.signup.landing .waysin { margin: 0; }
/* The Claude panel: the command and the token's first letters stay in one piece, and "Stored on ..." is not glued to the label. */
.signup.claude .how code { white-space: nowrap; }
.signup .tokenstate + .flabel, .signup .tokenstate + .inkwhy, .signup .hint + .flabel, .signup .steps + .flabel { margin-top: var(--space-4); }
.signup details.more .frozen { margin: var(--space-1) 0 0; }
.signup .drop input[type="file"], .signup .drop input[type="file"]::file-selector-button { font-size: var(--text-sm); }
.signup .drop input[type="file"]::file-selector-button { border-radius: var(--radius-md); padding: 10px 14px; }
@media (max-width: 480px) {
    .signup .panel { border-radius: var(--radius-lg); }
    .signup .codeslab code { font-size: var(--text-xl); }
    /* A phone's tap target: the two ways in on a signed-out page were 36px tall. */
    .waysin .btn { min-height: 44px; }
}
@media (prefers-reduced-motion: reduce) {
    .signup .spin, .relink .spin { animation: none; }
}
/* === end wac-signup === */

/* === wac-iphone: the import step for a database with no key (AppSignUp.IphoneGuide / AskPlain, 2026-10-01) - tokens only ===
   "Using an iPhone?" is a folded <details> under the Android steps, on the same card. Its summary is a 44px target (a
   phone-width tap), and the long backup-folder paths wrap instead of pushing the card sideways. The no-key card has two
   full-width buttons, stacked: Import (primary) and "Choose a different file". */
.signup details.iphone { margin: 0 0 16px; padding: 0 var(--space-4); border: 1px solid var(--line); border-radius: var(--radius-md); }
.signup details.iphone > summary { cursor: pointer; padding: 12px 0; min-height: 44px; box-sizing: border-box;
                                   font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--text); }
.signup details.iphone > summary:focus-visible { outline: var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }
.signup details.iphone .hint { margin: 0 0 12px; }
.signup details.iphone .guide { margin-bottom: var(--space-4); }
.signup details.iphone .guide li { overflow-wrap: anywhere; }
.signup .fieldrow.one .btn + .btn { margin-top: var(--space-2); }
/* === end wac-iphone === */

/* === wac-importwhy: what became of the uploaded backup (AppImportStatus.ts / AppImportRun.ts, 2026-10-01) - tokens only ===
   One block on the finished cards and above the import step: the service's sentence (`role=status`, written only when its
   words change) and, while it is still being worked on, a spinner and the elapsed count (`aria-hidden`, it ticks every
   second). `data-state` is the look word (`AppImportView.Look`): busy = ink on the page ground, done = the success notice
   pair, refused = ink on the danger tint, plain = ink on the page ground. Every text/ground pair is one `AppTokens.Pairs`
   measures. */
.signup .impwhy { display: flex; align-items: flex-start; gap: var(--space-2); margin: 0 0 var(--space-4); padding: var(--space-3);
                  border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--page); color: var(--text);
                  text-align: left; }
.signup .impwhy .impbody { flex: 1 1 auto; min-width: 0; }
.signup .impwhy .impline { margin: 0; font-size: var(--text-base); line-height: 1.45; overflow-wrap: anywhere; }
.signup .impwhy .impwait { margin: var(--space-1) 0 0; font-size: var(--text-sm); color: var(--muted); font-variant-numeric: tabular-nums; }
.signup .impwhy .impwait:empty { display: none; }
.signup .impwhy .spin { display: none; margin: 3px 0 0; }
.signup .impwhy[data-state="busy"] .spin { display: inline-block; color: var(--accent); }
.signup .impwhy[data-state="done"] { background: var(--success-soft); border-color: var(--success-line); color: var(--success-deep); }
.signup .impwhy[data-state="refused"] { background: var(--danger-soft); border-color: var(--danger-line); }
.signup .impwhy[data-state="done"] .impwait, .signup .impwhy[data-state="refused"] .impwait { display: none; }
.signup .implast { margin: 0 0 var(--space-4); }
.signup .implast .implasthead { margin: 0 0 var(--space-2); font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--text); }
.signup .implast .impwhy { margin-bottom: 0; }
/* === end wac-importwhy === */

/* === wac-audit: what the whole-app audit changed across components (2026-10-01, `capture-screens.mjs ONLY=audit`) - tokens only ===
   Every rule here is one the audit measured in real Chrome at 320 / 390 / 768 / 1280 / 1600, light and dark. Each part
   says what was found. (The type scale and the radii were brought onto the tokens in place, rule by rule, above.) */

/* ── 1. A FIELD'S EDGE IS 3:1 AGAINST WHAT IT SITS ON (`--line-strong`). The hairline `--line` (1.2:1) is right between
   rows of a card and wrong as the only thing saying "you can type here". Focus still turns the edge accent. */
.find input, .say textarea, .aask textarea, .signup .fieldrow input, .tpick input, .tnew input, .tnew select,
.trsel select { border-color: var(--line-strong); }
.find input:focus, .say textarea:focus, .aask textarea:focus, .signup .fieldrow input:focus { border-color: var(--accent); }
.wac-pal .palbox { border-bottom-color: var(--line-strong); }
/* An empty tick is known by its ring alone. */
.task .tick { border-color: var(--line-strong); }
.task .tick.treply { border-color: var(--accent); }

/* ── 2. THE CHAT LIST AS ROWS IN A NARROW COLUMN (a phone, the three-pane screen's list): the name and who they are share
   line 1 - the name whole up to 62% of the row, the relationship taking what is left and cut with an ellipsis (its whole
   text is the row's tooltip) - and line 2 is the state, the age and the counts, never cut. Before, the relationship
   shared line 2 with the state and was squeezed to "Kitc…" beside "Waiting on you · today · 6 to do". */
@media (max-width: 520px) {
    .asrows .card a { grid-template-columns: 44px fit-content(62%) minmax(0, 1fr); }
    .asrows .card .t { grid-row: 1; grid-column: 2; }
    .asrows .card .r { grid-row: 1; grid-column: 3; align-self: baseline; min-width: 0; margin-top: 0; }
    .asrows .card .m { grid-row: 2; grid-column: 2 / 4; text-align: left; white-space: normal; font-size: var(--text-xs); }
}
@media (min-width: 1100px) {
    .paneleft .asrows .card a { grid-template-columns: 44px fit-content(62%) minmax(0, 1fr); }
    .paneleft .asrows .card .t { grid-row: 1; grid-column: 2; }
    .paneleft .asrows .card .r { grid-row: 1; grid-column: 3; align-self: baseline; min-width: 0; margin-top: 0; }
    .paneleft .asrows .card .m { grid-row: 2; grid-column: 2 / 4; text-align: left; white-space: normal; }
    /* The five view tabs on ONE line above the list (they wrapped to two at 1280). */
    .paneleft { flex-basis: clamp(320px, 26vw, 380px); }
    /* ⭐ THE TOP BAR FOLLOWS THE PANES: the wordmark over the list, the search starting where the center pane starts
       (it ran from beside the wordmark to mid-screen, lining up with nothing). */
    html.wac-three .topline .brand { flex: 0 0 calc(clamp(320px, 26vw, 380px) - var(--space-4)); }
}
.asrows .card .t, .asrows .card .r { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The open chat's row: the accent edge AND a bolder name - not the tint alone. */
.paneleft .card a[aria-current="page"] .t { font-weight: 700; }

/* ── 3. (wave B, 2026-10-13: the view tabs are the navigation rail now - a bottom bar of 56px columns below 1100px; see
   "The navigation rail" near the top of this file.) */

/* ── 4. TAP TARGETS ON A PHONE: every control's hit area is 44 x 44 px or more. Buttons, chips, fields and menu items
   grow to it; a small mark (a task's tick, a "more" menu, a text link in a row) keeps its size and gets an unseen
   44px area centred on it (`::after`, or `::before` where `::after` is already drawn). */
@media (max-width: 620px) {
    .btn, .btn.sm, .signup .btn.sm { min-height: 44px; }
    .btn.icon, .btn.icon.sm { width: 44px; min-width: 44px; }
    .chip, .tf, .trchip, .menuitem, .tpop button, .tnew .btn, .wac-pal .palopt { min-height: 44px; }
    .chip, .tf, .trchip { display: inline-flex; align-items: center; }
    .find input, .tnew input, .tnew select, .trsel select, .tpick input { min-height: 44px; }
    .say textarea, .aask textarea { height: 44px; }
    .say .btn, .ago.btn { height: 44px; }
    .aibtn.btn { width: 44px; }
    .topstatus { min-width: 44px; min-height: 44px; justify-content: center; }
    .trrow { min-height: 44px; }
    .task .tick, .tmenu summary, .task .tmsg, .tasktoday .seeall, .tasktoday .more a, .tasktoday .tthead, .foot a,
    .tgroup.tfold > summary, .signup details.more summary, .hit a, .ovlist a, .ovbars a, .aforget .btn, .atools .btn,
    .tnx.btn, .xportnote a { position: relative; }
    .task .tick::after, .tmenu summary::after, .task .tmsg::after, .tasktoday .seeall::after, .tasktoday .more a::after,
    .tasktoday .tthead::after, .foot a::after, .tgroup.tfold > summary::after, .signup details.more summary::after,
    .aa .alink::before, .aibtn::before {
        content: ""; position: absolute; top: min(0px, calc(50% - 22px)); bottom: min(0px, calc(50% - 22px));
        left: min(0px, calc(50% - 22px)); right: min(0px, calc(50% - 22px));
    }
    .aa .alink { position: relative; }
    /* The footer's three links: room between them, so their areas do not lie on each other. */
    .foot { line-height: 2.6; }
    .foot a { display: inline-block; padding: 0 var(--space-1); }
}

/* ── 5. CONTRAST, AS RENDERED ── */
/* The count of matches under the phone's search box sits on the bar's surface. */
.bar .found { color: var(--muted); }
/* A lit tab's and a lit filter's count: full strength (it was the label's colour at 75%). */
.tab i, .tf i { color: inherit; }
/* === end wac-audit === */
/* === wac-audit2: the newer screens brought onto the design system (2026-10-12, `capture-screens.mjs ONLY=audit`) - tokens only ===
   The file row under the message box, the message search's filters / hits / paging and the "message in its
   conversation" window were added after the first audit with the browser's own controls and typed sizes. */

/* ── 1. THE FILE ROW (`AppComposerView.Attach`). The real `<input type="file">` is kept off screen (still focusable);
   what is seen is its label, a `btn secondary sm`, which draws the input's focus ring. */
/* ⚠ `position: relative` (verifier, 2026-10-12): the off-screen picker is `position: absolute`; with no positioned
   ancestor it did not scroll with the page's main part, so it stretched the DOCUMENT (99px) and focusing it could
   shift the whole page under `overflow: hidden`. Anchored to its own row it goes where the row goes. */
.attachment-controls { position: relative; flex-basis: 100%; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
    min-width: 0; padding: 0 var(--space-1); font-size: var(--text-sm); color: var(--text); }
.attachment-controls .attachin { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); opacity: 0; }
.attachment-controls .attachbtn.btn { height: auto; gap: var(--space-1); cursor: pointer; border-radius: var(--radius-md); }
.attachment-controls .attachin:focus-visible + .attachbtn { outline: var(--focus-ring); outline-offset: 2px; }
.attachment-controls .attname { min-width: 0; overflow-wrap: anywhere; font-weight: 600; }
.attachment-controls .attclear.btn { height: 30px; }
.attachment-controls .voicenote { display: inline-flex; align-items: center; gap: var(--space-2); min-height: 30px; cursor: pointer; }
.attachment-controls .voicenote input { width: 18px; height: 18px; margin: 0; accent-color: var(--accent); }
.attachment-controls [hidden] { display: none; }
@media (max-width: 700px) {
    .attachment-controls .attachbtn.btn, .attachment-controls .voicenote { min-height: 44px; }
    .attachment-controls .attclear.btn { width: 44px; height: 44px; }
}

/* ── 2. THE MESSAGE SEARCH'S FILTERS (`AppSearchPanel.Controls`): each one a labelled field - the label above its
   control - in a row that wraps; the controls are the app's own (the field edge, the radius, the focus ring), and on a
   phone each is 44px tall and the contact picker and the dates take the row's whole width (the picker's longest name
   pushed the page 33px sideways at 390). */
.search-filters { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-3); min-width: 0; margin: 0 0 var(--space-3);
    padding: var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); }
.search-filters > legend { padding: 0 var(--space-1); font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--muted); }
.search-filters .sf { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; margin: 0; padding: 0; border: 0;
    font-size: var(--text-sm); color: var(--muted); }
.search-filters .sf > legend { padding: 0; margin: 0 0 var(--space-1); }
.search-filters .sf select, .search-filters .sf input[type="date"] {
    font: inherit; font-size: var(--text-base); color: var(--text); background: var(--page); min-width: 0; max-width: 100%;
    min-height: 36px; padding: var(--space-1) var(--space-2); border: 1px solid var(--line-strong); border-radius: var(--radius-md);
}
.search-filters .sf select { max-width: 240px; }
.search-filters .sf select:focus-visible, .search-filters .sf input[type="date"]:focus-visible {
    outline: var(--focus-ring); outline-offset: 1px; border-color: var(--accent); }
.search-filters .sfopts { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); min-height: 36px; align-items: center; }
.search-filters .sfopt { display: inline-flex; align-items: center; gap: var(--space-1); color: var(--text); font-size: var(--text-base); cursor: pointer; }
.search-filters .sfopt input { width: 18px; height: 18px; margin: 0; accent-color: var(--accent); }
@media (max-width: 700px) {
    .search-filters .sf:not(.sfsender) { flex: 1 1 100%; }
    /* The two dates share one row (each a little under half), so the results are not pushed a screen down. */
    .search-filters .sf:has(input[type="date"]) { flex: 1 1 calc(50% - var(--space-3)); }
    .search-filters .sf input[type="date"] { width: 100%; }
    .search-filters .sf select { max-width: 100%; width: 100%; }
    .search-filters .sf select, .search-filters .sf input[type="date"], .search-filters .sfopt { min-height: 44px; }
    .search-filters .sfopt { padding: 0 var(--space-1); }
}
/* A hit the service found opens the message in its conversation: a button drawn as the link beside it (`.hit a`). */
.hits .hit button { display: block; width: 100%; text-align: left; font: inherit; color: inherit; cursor: pointer; padding: 9px 13px;
    background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-lg); min-width: 0; }
.hits .hit button:hover { background: var(--soft); }
.hits .hit button .who, .hits .hit button .when { display: inline; }
.hitsmore { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-3) 0 0; }
@media (max-width: 700px) { .hitsmore .btn { min-height: 44px; } }
/* The searching ring: decoration beside the words "Searching messages…"; still for a reader who asked for less motion. */
.wac-search-busy::after { content: ""; display: inline-block; width: 16px; height: 16px; margin-left: var(--space-2); vertical-align: middle;
    border: 2px solid var(--accent); border-right-color: transparent; border-radius: var(--radius-pill); animation: wac-search-spin .8s linear infinite; }
@keyframes wac-search-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .wac-search-busy::after { animation: none; border-right-color: var(--accent-line); } }

/* ── 3. A MESSAGE IN ITS CONVERSATION (`AppMessageContext`): one of the app's dialogs - the family's edge, shadow and
   backdrop - with the messages as rows, the owner's own on the bubble green, the one that was asked for ringed. */
.message-context { border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); color: var(--text);
    padding: var(--space-4) var(--space-5) var(--space-5); width: min(640px, calc(100vw - 32px)); max-height: min(85vh, 720px);
    overflow-y: auto; box-shadow: 0 16px 48px rgba(16, 20, 28, .28); font-family: var(--font-ui); font-size: var(--text-base); }
.message-context::backdrop { background: rgba(16, 20, 28, .45); }
.message-context .mchead { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin: 0 0 var(--space-3); }
.message-context h2 { margin: 0; font-size: var(--text-lg); font-weight: var(--weight-strong); }
.message-context-body { display: grid; gap: var(--space-2); color: var(--text); }
.message-context .mcrow { display: flex; flex-direction: column; gap: 2px; padding: var(--space-2) var(--space-3);
    border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--page); min-width: 0; }
.message-context .mcrow.me { background: var(--mebubble); border-color: var(--accent-line); }
.message-context .mcwho { font-size: var(--text-xs); color: var(--muted); }
.message-context .mcrow.me .mcwho { color: var(--text); }
.message-context .mctext { white-space: pre-wrap; overflow-wrap: anywhere; }
.message-context .message-context-target { border: 2px solid var(--accent); }
@media (max-width: 700px) { .message-context { padding: var(--space-3) var(--space-3) var(--space-4); } .message-context .mchead .btn { width: 44px; height: 44px; min-height: 44px; } }

/* ── 4. NOTHING CUT WITH NO WAY TO THE REST, ON A PHONE: the chat header's second line ("Kitchen customer · Waiting on
   you · last message today · 38 messages") and a long name in the Ctrl+K list wrap instead of ending in an ellipsis
   (neither is a control, so a tooltip could not be asked for by the keyboard). A chat row's cut text is the row link's
   own `title` (`AppViews.WholeRow`). */
@media (max-width: 700px) {
    .chsub { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
    .wac-pal .palt { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
    /* ── 5. TWO 44px TAP TARGETS THE FIRST AUDIT MISSED: the Ctrl+K box (33px) and the import page's
       "Back to my archive" link (17px - a line of text with nothing around it). */
    .wac-pal .palbox input { min-height: 44px; }
    /* The Overview's rows are links stacked one on the next (32-35px each): each is 44px tall itself - an unseen area
       around a row would lie on the rows above and below it. */
    .ovlist a, .ovbars a { min-height: 44px; }
    .signup .backlink a { display: inline-flex; align-items: center; min-height: 44px; }
}

/* ── 6. THE CONVERSATION KEEPS A FLOOR (verifier, 2026-10-12; `capture-screens.mjs ONLY=audit AUDIT_PART=verify-crowd`).
   On a 390x700 phone with the account band AND the "Link WhatsApp again" banner AND a chosen file the chat was 61px
   tall - not one message could be read - because everything above and below it is fixed and it alone gives way. Below
   three panes the frame now never goes under 260px: when the rest does not fit, the page's main part scrolls (the
   banner scrolls away; the message box is at the end of that scroll). With room - every ordinary state, and the band
   or the banner alone on a 700px-tall phone - nothing changes: the chat still fills the screen and nothing scrolls. */
@media (max-width: 1099px) {
    /* ⚠ `position: relative`: a visually hidden `.sr` note inside (absolutely placed, no positioned ancestor) would stay
       where it was laid out while this scrolls, and stretch the document under `overflow: hidden`. */
    body.detail main { overflow-y: auto; position: relative; }
    body.detail .contact { min-height: auto; flex-shrink: 0; }
    body.detail .chatcard { min-height: min-content; }
    body.detail #wac-frame { min-height: 260px; }
}
/* ── 6b. STILL CUT AT 320 AND 768 (verifier: the audit ran 390 / 1280 / 1600 only). The chat header's name, and its second
   line beside the export note on a tablet; the Ctrl+K list's second line on a 320px screen. None is a control, so each
   wraps instead of ending in an ellipsis nobody can open. */
@media (max-width: 1099px) {
    .chname, .chsub { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
}
@media (max-width: 700px) {
    .wac-pal .pals { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
}
/* The chosen file's name takes the row's free width with its remove button beside it (it wrapped to a line of its
   own, and the button to a third). */
.attachment-controls .attname { flex: 1 1 12em; }

/* ── 7. THE APP'S OWN TOOLTIP (`AppTips`, 2026-10-02) HAD NO RULE HERE (verifier, 2026-10-12). `AppTipRules.Css` says
   "the app's is in app.css" - it was not, so in the app's document the tip was an unstyled block at the END of <body>.
   On a chat (`body.detail` is a column exactly one screen tall) it took 43px from the conversation while a control with
   a `title` had the keyboard's focus - and the moment a mouse press moved that focus the tip went, the message box and
   Send jumped down 35px between the press and its release, and the click landed on the form: Send did nothing
   (measured at 320x640 with a file chosen - the picker keeps the focus after a choice). Kept equal to
   `AppTipRules.Css()` by `AppAuditVerify.test.ts`. */
.wac-tip { position: fixed; z-index: 60; max-width: min(320px, calc(100vw - 16px)); padding: 6px 10px; border-radius: var(--radius-md);
    background: var(--text); color: var(--surface); font-size: var(--text-xs); line-height: 1.4;
    box-shadow: 0 4px 16px rgba(16, 20, 28, .25); pointer-events: none; white-space: normal; overflow-wrap: anywhere; }
.wac-tip[hidden] { display: none; }
/* === end wac-audit2 === */

/* === wac-business: the business look, wave A "surface" (owner, 2026-10-13; documentation/mockups/business-look.html) - tokens only ===
   What the mockup shows for colour, shape and panels, with the markup as it stands (the navigation rail, the slim top
   bar, the structured contact header and the table list are wave B). It comes last, so it wins at equal weight.

   ── 1. WHICH CHATS TO SHOW (`AppChatListRules.Switch`: All / Starred / Archived): underlined words, like the view tabs -
   the chosen one in the accent with a 2px rule under it (they were three ghost buttons, the chosen one tinted). */
.chatviews { gap: 0 var(--space-3); margin: 0 0 var(--space-2); border-bottom: 1px solid var(--line); }
.chatviews .btn { border: 0; border-bottom: 2px solid transparent; border-radius: 0; margin-bottom: -1px; padding-left: 2px; padding-right: 2px;
                  color: var(--muted); font-weight: var(--weight-regular); }
.chatviews .btn:not(:disabled):hover { background: transparent; color: var(--text); }
.chatviews .btn[aria-pressed="true"] { background: transparent; color: var(--accent); border-bottom-color: var(--accent); font-weight: var(--weight-strong); }
.chatviews .btn:focus-visible { outline-offset: -2px; border-radius: var(--radius-sm); }

/* ── 3. THE CHAT HEADER'S OPEN-CHAT ROW AND A SELECTED ROW: the accent edge and tint say "this one" (as before); a row
   under the pointer is the page tint. Nothing here is round but the disc. */
.asrows .card a:hover { background: var(--page); }

/* ── 4. FLAT PANES ON THE THREE-PANE SCREEN (>= 1100px, `html.wac-three`). The panes are divided by 1px rules and fill
   the height; nothing inside a pane is a rounded, bordered card of its own:
     left   - the view tabs, the chat-list switches and the rows run edge to edge;
     centre - with a chat open: the header is a surface bar with a rule under it, then the task strip, the thread and
              the message box, each separated by a rule (the frame already draws its own);
     right  - the AI panel is a real pane: a rule on its left, a plain header on the surface.
   ⚠ A LIST VIEW in the centre (Choose a chat, Tasks, Overview, Revenue, Sent) keeps its bordered panels on the page
   ground - they are documents in a reading column, not panes. */
@media (min-width: 1100px) {
    html.wac-three .paneleft { padding: 0; }
    html.wac-three .paneleft .chatviews { margin: 0; padding: 0 var(--space-3); }
    html.wac-three .paneleft .cards.asrows { border: 0; border-radius: 0; border-bottom: 1px solid var(--line); }
    html.wac-three .paneleft > p, html.wac-three .paneleft .msghits, html.wac-three .paneleft .none { margin-left: var(--space-3); margin-right: var(--space-3); }
    html.wac-three .paneleft .msghits { margin-bottom: var(--space-4); }
    /* The centre with a chat open: no padding - the bars reach the pane's edges. */
    html.wac-three body.detail .panecenter { padding: 0; }
    html.wac-three body.detail .panecenter > .chathead { padding: var(--space-2) var(--space-3) var(--space-2) var(--space-4); min-height: 56px; box-sizing: border-box;
                                                         background: var(--surface); border-bottom: 1px solid var(--line); }
    html.wac-three body.detail .panecenter > .relink { margin: var(--space-3) var(--space-4) 0; }
    html.wac-three body.detail .panecenter > .trline { margin: var(--space-2) var(--space-4); }
    html.wac-three body.detail .panecenter > .skelchat { margin: var(--space-3) var(--space-4); }
    html.wac-three .contact { gap: 0; }
    html.wac-three .chatcard { border: 0; border-radius: 0; background: var(--page); }
    html.wac-three .chatcard > .say, html.wac-three .chatcard > .sayoff { background: var(--surface); padding: 10px var(--space-3) 10px var(--space-4); }
    html.wac-three .contact.ai-on .assist { border: 0; border-left: 1px solid var(--line); border-radius: 0; }
    html.wac-three .ahead { min-height: 56px; box-sizing: border-box; }
}
/* === end wac-business === */

/* === wac-structure: the business look, wave B "structure" (owner, 2026-10-13; documentation/mockups/business-look.html) - tokens only ===
   The markup changes that go with wave A's surface: the navigation rail (`AppRail`; its bottom-bar form is near the top
   of this file), the slim light top bar (`AppTopBar`), the structured contact header (`AppChatHead`), the compact /
   table-like chat list (`AppViews`), and the AI panel as a true right pane. It comes last, so it wins at equal weight.
   ⛔ ONE BREAKPOINT for the panes and the rail's two forms: 1100px (`AppLayout.WideMin`). */

/* ── 1. THE TOP BAR: about 44px, the mark and the wordmark, a compact search, quiet icon buttons ── */
.topline { min-height: 44px; gap: 0 var(--space-2); }
.brand { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-base); color: var(--text); white-space: nowrap; }
.brandmark { display: inline-flex; flex: none; }
/* The version after the wordmark: small, muted (muted on surface >= 4.5:1 in both themes, AppTokens.Pairs). */
.brandver { font-family: var(--font-ui); font-size: var(--text-xs); font-weight: var(--weight-regular); color: var(--muted); }
.brandmark svg { display: block; }
.wi-mark .mark-bg { fill: var(--accent); }
.wi-mark .mark-fg { fill: var(--on-accent); }
body.detail .bar { padding: 0 var(--space-2) 0 var(--space-3); }
/* The search field: the magnifier inside it on the left (decoration; the field's label names it). */
.find:has(> .findico) { display: grid; grid-template-columns: minmax(0, 1fr); align-items: center; }
.find .findico { grid-area: 1 / 1; justify-self: start; z-index: 1; margin-left: 10px; display: inline-flex; color: var(--muted); pointer-events: none; }
.find .findico svg { display: block; }
.find .findico + input { grid-area: 1 / 1; padding-left: 34px; }
.find .found { grid-row: 2; }
.topsearch { position: relative; flex: 0 1 420px; max-width: 420px; }
.topsearch .find input { height: 32px; padding-top: 0; padding-bottom: 0; font-size: var(--text-sm); background: var(--page); }
/* The shortcut's keycaps beside the field: wide screens only, and gone the moment something is typed (the browser's
   own clear button sits there then). */
.findkeys { display: none; position: absolute; right: 6px; top: 6px; gap: 3px; pointer-events: none; }
.findkeys kbd { display: inline-flex; align-items: center; justify-content: center; min-width: 22px; height: 20px; padding: 0 5px; box-sizing: border-box;
                font: inherit; font-size: var(--text-xs); line-height: 1; color: var(--muted); background: var(--surface);
                border: 1px solid var(--line); border-radius: var(--radius-sm); }
@media (max-width: 799px) {
    /* The phone's search is the bar's second line: full width, a little air under it. */
    .topsearch { flex: 0 0 100%; max-width: none; margin: 0 0 var(--space-2); }
    .topsearch .find input { height: 36px; }
}

/* The narrowest phones (320px): the mark, the wordmark, the status and the two buttons still share ONE line. */
@media (max-width: 360px) {
    .bar, body.detail .bar { padding: 0 2px 0 var(--space-2); }
    .topline { gap: 0 2px; }
    .brand { gap: 6px; font-size: var(--text-sm); }
    .topright { gap: 0; }
}

/* ── 2. THE CHAT LIST AS ROWS: one line each - disc, name, who they are (cut with an ellipsis; the row's tooltip has
   the whole of it), then on the right how long ago and the badges. The standing is the row's stripe here; the words
   ("Waiting on you") come back as a column where the list is the main content (the table, below). ── */
.listhead { display: none; }
.cards.asrows > .card > a { display: flex; align-items: center; gap: 10px; min-height: 44px; padding: 0 46px 0 9px;
                            font-size: var(--text-sm); white-space: nowrap; }
.cards.asrows > .card > a > .av { flex: none; width: 28px; height: 28px; font-size: var(--text-xs); }
.cards.asrows > .card > a > .t { align-self: center; flex: 0 1 auto; min-width: 0; max-width: 62%; overflow: hidden; text-overflow: ellipsis; }
.cards.asrows > .card > a > .r { align-self: center; flex: 1 1 0; min-width: 0; margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--text-sm); }
.cards.asrows > .card > a > .m { align-self: center; flex: none; display: inline-flex; align-items: center; gap: var(--space-2); margin: 0 0 0 auto;
                                 font-size: var(--text-xs); text-align: right; white-space: nowrap; }
.asrows .m .ms, .asrows .m .msep { display: none; }
.asrows .m .mb { display: inline-flex; align-items: center; gap: var(--space-1); }
.asrows .m .mb:empty { display: none; }
/* "typing…" on a row is never the part that gives way (the name is, with its ellipsis and the row's tooltip). */
.cards.asrows > .card > a > .r.wac-typing { flex: 0 0 auto; overflow: visible; }
/* The row's "more" button: centred on the row, a 32px square for a mouse. */
.cards.asrows > .card > .chatquick-open { top: 50%; right: 6px; transform: translateY(-50%); width: 32px; height: 32px; min-height: 0; }
@media (max-width: 620px) {
    /* A phone: 52px rows, and the "more" button a full 44px target. */
    .cards.asrows > .card > a { min-height: 52px; padding-right: 50px; }
    .cards.asrows > .card > .chatquick-open { right: 2px; width: 44px; height: 44px; }
}
/* The narrowest phones (320px): the NAME is what must be whole (a number-only chat was cut to a few digits), and
   one line cannot hold it beside the age and a badge. There a row is two short lines: the name, then the age and the
   badges under it (the relationship, which had already given way to an ellipsis, is in the row's tooltip). */
@media (max-width: 360px) {
    .cards.asrows > .card > a { flex-wrap: wrap; row-gap: 0; padding-top: 6px; padding-bottom: 6px; }
    .cards.asrows > .card > a > .t { flex: 1 1 60%; max-width: none; }
    .cards.asrows > .card > a > .r:not(.wac-typing) { display: none; }
    .cards.asrows > .card > a > .m { flex: 0 0 calc(100% - 38px); margin-left: 38px; justify-content: flex-start; }
}
/* The table: where the list is the page's main content and there is room for columns (800-1099px). A header row for
   the eye (`AppViews.ListHead`, aria-hidden - each row already says its own words in order) and the same pieces of a
   row laid on one grid: Name | Relationship | Status | Last message | Open tasks. */
@media (min-width: 800px) and (max-width: 1099px) {
    .listhead, .cards.asrows > .card > a { display: grid; grid-template-columns: 28px minmax(0, 1.3fr) minmax(0, 1.3fr) minmax(0, 1fr) 96px 128px;
                                           column-gap: var(--space-3); align-items: center; }
    .listhead { height: 32px; padding: 0 46px 0 13px; background: var(--page); color: var(--muted); font-size: var(--text-xs);
                font-weight: var(--weight-strong); border: 1px solid var(--line); border-bottom: 0;
                border-radius: var(--radius-lg) var(--radius-lg) 0 0; white-space: nowrap; }
    .listhead span:first-child { grid-column: 1 / 3; }
    .listhead + .cards.asrows { border-top-left-radius: 0; border-top-right-radius: 0; }
    .cards.asrows > .card > a > .av { grid-area: 1 / 1; }
    .cards.asrows > .card > a > .t { grid-area: 1 / 2; max-width: none; }
    .cards.asrows > .card > a > .r { grid-area: 1 / 3; }
    .cards.asrows > .card > a > .m { display: contents; }
    .asrows .m .ms { grid-area: 1 / 4; text-align: left; }
    .asrows .m .ma { grid-area: 1 / 5; text-align: left; }
    .asrows .m .mb { grid-area: 1 / 6; }
    .asrows .m .ms { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; font-size: var(--text-sm); color: var(--muted); }
    .asrows .card.s-owed .m .ms { color: var(--warning); font-weight: var(--weight-strong); }
    .asrows .m .ma { font-size: var(--text-sm); color: var(--muted); }
    .asrows .m .mb, .asrows .m .mb:empty { display: inline-flex; min-width: 0; }
}

/* ── 3. THE CONTACT HEADER: the disc, the name, then a row of small labels over values (`AppChatHead.Props`, a real
   `dl`). Wide: beside the name behind a hairline, wrapping to a second line when the pane is narrow. Below the panes:
   a three-column grid under the name. A value wraps rather than ending in an ellipsis nobody can open. ── */
/* ⛔ THE PROPERTY ROW IS A GRID WHOSE COLUMNS COME FROM THE HEADER'S OWN WIDTH AND THE NUMBER OF FIELDS (2026-10-13).
   It was a wrapping flex row: beside the open AI pane at 1280 six fields broke 3 + 2 + 1, "Open tasks" alone on a third
   line. The header is a size container (`chhead`) because its width follows the AI pane, not the window:
     >= 840px of header: ONE row beside the name (a column per field, each as wide as its words);
     <  840px          : under the name, three equal columns (6 = 3 + 3, 5 = 3 + 2; four fields are 2 + 2, or one row
                         of four from 480px) - never more than two rows, never a row holding a single field of 4+;
     <  340px          : two columns, and the fields the row can do without (`.lo`: Last message, Messages) give way.
   `data-n` / `data-core` are counts written by `AppChatHead.Props`. */
.chathead { gap: var(--space-3); container: chhead / inline-size; }
.chathead .av { width: 36px; height: 36px; font-size: var(--text-sm); }
.chid { flex: 0 1 auto; max-width: 34%; }
.chprops { flex: 1 1 0; min-width: 0; display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, max-content);
           justify-content: start; align-items: start; gap: var(--space-1) var(--space-5); margin: 0 0 0 var(--space-1);
           padding: 0 0 0 var(--space-4); border-left: 1px solid var(--line); }
.chprops > div { min-width: 0; max-width: 32ch; }
.chprops dt { font-size: var(--text-xs); line-height: 16px; color: var(--muted); white-space: nowrap; }
.chprops dd { margin: 0; font-size: var(--text-sm); line-height: 18px; overflow-wrap: break-word; }
.chprops dd.wait { color: var(--warning); font-weight: var(--weight-strong); }
.chacts { margin-left: auto; }
/* Below the panes the header is the window's width: the same stacked form, said by the window too (a browser without
   container queries still gets it there). */
@media (max-width: 1099px) {
    .chid { flex: 1 1 0; max-width: none; }
    .chprops { order: 5; flex: 0 0 100%; grid-column: 1 / -1; grid-auto-flow: row; grid-template-columns: repeat(3, minmax(0, 1fr));
               gap: 6px var(--space-3); margin: 0; padding: 0 0 var(--space-1); border-left: 0; }
    .chprops > div { max-width: none; }
    .chprops[data-n="4"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@container chhead (max-width: 839px) {
    .chid { flex: 1 1 0; max-width: none; }
    .chprops { order: 5; flex: 0 0 100%; grid-column: 1 / -1; grid-auto-flow: row; grid-template-columns: repeat(3, minmax(0, 1fr));
               gap: 6px var(--space-3); margin: 0; padding: 0 0 var(--space-1); border-left: 0; }
    .chprops > div { max-width: none; }
    .chprops[data-n="4"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@container chhead (min-width: 480px) and (max-width: 839px) {
    .chprops[data-n="4"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@container chhead (max-width: 339px) {
    .chprops, .chprops[data-n] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .chprops:not([data-core="0"]) > .lo { display: none; }
}
/* ⚠ A SHORT SCREEN (a small phone, a phone on its side): the conversation comes first. The fields that always stay run
   on as "label value" pairs in two columns - at most two lines, about half the height of the grid; Last message and
   Messages give way (the chat list's row says both). */
@media (max-width: 1099px) and (max-height: 760px) {
    .chprops, .chprops[data-n] { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--space-3); }
    .chprops > div { display: flex; align-items: baseline; gap: var(--space-1); }
    .chprops:not([data-core="0"]) > .lo { display: none; }
    .chprops dt, .chprops dd { font-size: var(--text-xs); line-height: 18px; }
    /* A short but WIDE window (a tablet or a phone on its side): the pairs fit on one line, each as wide as its words. */
    @container chhead (min-width: 640px) {
        .chprops, .chprops[data-n] { grid-template-columns: repeat(4, minmax(0, max-content)); gap: 0 var(--space-5); }
    }
}
/* ⚠ A SHORT SCREEN WITH A BAND (2026-10-13; `capture-audit-verify.mjs` verify-crowd, 390x700): the re-link banner
   (or the account band) takes ~180px, and with the property row under the name the conversation hit its 260px floor
   and the page's main part scrolled by 85px - the message box was under the navigation bar. While a banner or the band
   is showing, the property row gives way (the chat list's row and the page's Details say the same things) and the
   banner is set tighter: the conversation and the message box come before the contact's details. */
@media (max-width: 1099px) and (max-height: 760px) {
    body.detail:has(.relink) .chprops, body:has(#wac-standing:not([hidden])) .chprops { display: none; }
    body.detail .relink { margin-bottom: var(--space-2); padding: var(--space-2) var(--space-3); line-height: 1.4; }
    body.detail .relink p { margin-bottom: 6px; }
}
/* ⚠ A VERY SHORT SCREEN (under 680px: a small phone such as 320x640): with the bar, the message box and the
   navigation bar fixed, the conversation had about 100px left. The property row gives way entirely there (the chat
   list's row and the page's Details say the same things) and the navigation bar is 48px. */
@media (max-width: 1099px) and (max-height: 679px) {
    body.detail .chprops { display: none; }
    .rail .tab { min-height: 48px; }
    html.wac-rail body { padding-bottom: calc(48px + env(safe-area-inset-bottom, 0px)); }
}

/* ── 4. THE THREE PANES (>= 1100px): the rail is the shell's first column; the list pane's header is one line (which
   chats | rows or cards); with a chat open the AI panel is a pane from under the top bar to the footer. ── */
@media (min-width: 1100px) {
    html.wac-three body { padding-bottom: 0; }
    html.wac-three .bar { padding: 0 var(--space-2) 0 var(--space-3); }
    html.wac-three .topline .brand { flex: 0 0 196px; }
    html.wac-three .topsearch .findkeys { display: inline-flex; }
    html.wac-three .topsearch .find input { padding-right: 70px; }
    html.wac-three .topsearch:has(input:not(:placeholder-shown)) .findkeys { display: none; }
    html.wac-three .lefttabs { flex: 0 0 64px; min-height: 0; overflow-y: auto; background: var(--surface); border-right: 1px solid var(--line); }
    html.wac-three .lefttabs .rail { position: static; flex-direction: column; gap: 2px; padding: var(--space-2) 0; border-top: 0; }
    html.wac-three .lefttabs .rail .tab { flex: none; min-height: 0; padding: 8px 0 7px; }
    html.wac-three .lefttabs .rail .tab.on::before { right: auto; bottom: 0; width: 3px; height: auto; }
    /* A chat opened below the breakpoint carries its own bar; in the panes the column is the rail. */
    html.wac-three .panecenter .rail { display: none; }
    /* The list pane's header: All / Starred / Archived with the rows-or-cards switch at its right end. */
    html.wac-three .paneleft { position: relative; }
    html.wac-three .paneleft .chatviews { min-height: 40px; align-items: stretch; margin: 0; padding: 0 var(--space-3); }
    /* The AI panel: a pane of its own, the full height of the centre (it started under the chat header). */
    html.wac-three body.detail .panecenter { position: relative; }
    html.wac-three body.detail .panecenter:has(.contact.ai-on) { padding-right: 360px; }
    html.wac-three body.detail .panecenter .contact.ai-on .assist { position: absolute; top: 0; right: 0; bottom: 0; width: 360px; }
}
/* ⚠ CROSSED BELOW THE BREAKPOINT WITH A CHAT OPEN (the chat is not redrawn): the rail's holder gives way like the
   panes do, and the rail inside it is the bottom bar again (its base rules). */
@media (max-width: 1099px) {
    .lefttabs { display: contents; }
}
/* === end wac-structure === */

/* === wac-chatset: the "Chat settings" dialog (`AppChatSettingsView` / `AppChatSettingsDialog`, 2026-10-13) - tokens only ===
   A native modal <dialog>: a title bar, then sections of rows - a name with its value under it, the control at the
   right (a switch, or one square button per choice). On a phone a row of choices goes under its name, full width.
   ⛔ A row is never without a value: before the first answer it is a skeleton with "Checking with WhatsApp…" and its
   control is not drawn. ⚠ A control that cannot be used is `aria-disabled` (still reachable by keyboard, so its reason
   can be read) - it is drawn muted, never by opacity (its words must keep their contrast). */
.chatset { width: min(520px, calc(100vw - 24px)); max-width: none; max-height: calc(100dvh - 24px); box-sizing: border-box; padding: 0;
           border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); color: var(--text);
           font-family: var(--font-ui); font-size: var(--text-base); line-height: 1.4; overflow: hidden;
           box-shadow: 0 16px 48px rgba(16, 20, 28, .28); }
.chatset[open] { display: flex; flex-direction: column; }
.chatset::backdrop { background: rgba(16, 20, 28, .45); }
.cs { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.cs-head { flex: none; display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
           padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4); border-bottom: 1px solid var(--line); }
.cs-headtext { min-width: 0; }
.cs-head h2 { margin: 0; font-size: var(--text-md); font-weight: var(--weight-strong); line-height: 1.3; }
.cs-who { margin: 0; font-size: var(--text-sm); color: var(--muted); overflow-wrap: anywhere; }
.cs-close { flex: none; color: var(--muted); }
.cs-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0 var(--space-4) var(--space-4); }
.cs-sec { padding-top: var(--space-3); }
.cs-sec + .cs-sec { margin-top: var(--space-2); border-top: 1px solid var(--line); }
.cs-sec[hidden], .cs-fail[hidden], .cs-trpanel[hidden] { display: none; }
.cs-sec h3 { margin: 0; font-size: var(--text-xs); line-height: 16px; font-weight: var(--weight-strong); color: var(--muted); }
.cs-fail { padding-top: var(--space-4); }
.cs-fail p { margin: 0 0 var(--space-3); }
.cs-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--space-1) var(--space-3);
          min-height: 48px; padding: var(--space-2) 0; box-sizing: border-box; }
.cs-row + .cs-row { border-top: 1px solid var(--line); }
.cs-text { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.cs-name { font-weight: var(--weight-strong); }
.cs-state { display: flex; flex-wrap: wrap; align-items: center; gap: 0 var(--space-2); min-height: 18px;
            font-size: var(--text-sm); line-height: 18px; color: var(--muted); }
.cs-words { color: var(--text); }
.cs-secs { font-variant-numeric: tabular-nums; }
.cs-secs:empty { display: none; }
.cs-skel { width: 88px; height: 10px; }
.cs-words[hidden], .cs-hint[hidden], .cs-skel[hidden], .cs-note[hidden], .cs-why[hidden], .cs-switch[hidden], .cs-seg[hidden] { display: none; }
.cs-ctl { justify-self: end; display: flex; align-items: center; min-width: 0; }
.cs-note, .cs-why { grid-column: 1 / -1; margin: 0; font-size: var(--text-sm); line-height: 1.45; overflow-wrap: anywhere; }
.cs-note { color: var(--text); }
.cs-why { color: var(--muted); }
/* The switch: a square track with a square thumb (no pills). The button IS the track (its own edge carries the 3:1
   boundary); an unseen area around it makes the hit area 44 x 44 px. */
.cs-switch { position: relative; flex: none; display: block; width: 36px; height: 20px; margin: 6px 4px; padding: 0; box-sizing: border-box;
             border: 1px solid var(--line-strong); border-radius: var(--radius-sm); background: var(--line-strong); cursor: pointer;
             transition: background-color .12s, border-color .12s; }
.cs-switch::after { content: ""; position: absolute; top: -13px; bottom: -13px; left: -5px; right: -5px; }
.cs-switch:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.cs-thumb { position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: var(--radius-sm); background: var(--surface);
            transition: transform .12s; }
.cs-switch[aria-checked="true"] { background: var(--accent); border-color: var(--accent); }
.cs-switch[aria-checked="true"] .cs-thumb { transform: translateX(16px); }
.cs-switch[aria-disabled="true"] { cursor: default; background: var(--page); border-color: var(--line-strong); }
.cs-switch[aria-disabled="true"] .cs-thumb { background: var(--line-strong); }
.cs-switch[aria-disabled="true"][aria-checked="true"] { background: var(--accent-soft); border-color: var(--accent); }
.cs-switch[aria-disabled="true"][aria-checked="true"] .cs-thumb { background: var(--accent); }
.cs-row[aria-busy="true"] .cs-switch, .cs-row[aria-busy="true"] .cs-opt { cursor: progress; }
/* The choices: one segmented row of square buttons, the current one lit (like the rows-or-cards switch). */
.cs-seg { display: flex; }
.cs-seg .cs-opt { border-radius: 0; margin-left: -1px; color: var(--text); border-color: var(--line-strong); white-space: nowrap; }
.cs-seg .cs-opt:first-child { margin-left: 0; border-radius: var(--radius-md) 0 0 var(--radius-md); }
.cs-seg .cs-opt:last-child { border-radius: 0 var(--radius-md) var(--radius-md) 0; }
.cs-seg .cs-opt[aria-pressed="true"] { position: relative; color: var(--accent); background: var(--accent-soft); border-color: var(--accent);
                                       font-weight: var(--weight-strong); }
.cs-seg .cs-opt[aria-disabled="true"] { color: var(--muted); background: var(--page); cursor: default; }
.cs-seg .cs-opt[aria-disabled="true"]:hover { background: var(--page); border-color: var(--line-strong); }
.cs-seg .cs-opt[aria-disabled="true"][aria-pressed="true"] { color: var(--accent); background: var(--accent-soft); border-color: var(--accent); }
.cs-seg .cs-opt:focus-visible { position: relative; z-index: 1; }
/* Translation: the settings that were an inline panel under the chat header. */
.cs-tr .trline { margin: var(--space-2) 0 0; }
.cs-trpanel { padding-top: var(--space-2); }
@media (max-width: 480px) {
    .cs-row:has(.cs-seg) { grid-template-columns: minmax(0, 1fr); }
    .cs-row:has(.cs-seg) .cs-ctl { justify-self: stretch; }
    .cs-row:has(.cs-seg) .cs-seg { flex: 1 1 auto; }
    .cs-seg .cs-opt { flex: 1 1 0; min-width: 44px; padding-left: 4px; padding-right: 4px; }
}
@media (prefers-reduced-motion: reduce) { .cs-switch, .cs-thumb { transition: none; } }
/* === end wac-chatset === */
/* === wac-chatactions: pin / read / block / clear / delete rows, "About this contact", the list's phone marks, New chat
   (`AppChatSettingsView`, `AppViews.PhoneMarks`, `AppNewChat`, 2026-10-14) - tokens only === */
.cs-ask[hidden], .cs-confirm[hidden], .cs-lock[hidden], .cs-info[hidden], .cs-dl > div[hidden] { display: none; }
.cs-confirm { grid-column: 1 / -1; margin: 0; padding: var(--space-3); border: 1px solid var(--line-strong); border-radius: var(--radius-md);
    background: var(--surface); }
.cs-confirm p { margin: 0 0 var(--space-2); font-size: var(--text-sm); line-height: 1.45; color: var(--text); }
.cs-confirmacts { display: flex; flex-wrap: wrap; gap: var(--space-2); }
/* The confirm step lives inside the row's control cell: while it is open that cell takes the whole row, under the name. */
.cs-row:has(.cs-confirm:not([hidden])) .cs-ctl { grid-column: 1 / -1; display: flex; flex-direction: column; align-items: stretch; gap: var(--space-2); }
.cs-row:has(.cs-confirm:not([hidden])) .cs-ask { align-self: flex-end; }
.cs-lock { display: flex; align-items: center; gap: var(--space-2); margin: 0 0 var(--space-3); font-size: var(--text-sm); color: var(--text); }
.cs-dl { margin: 0; display: grid; gap: var(--space-2); }
.cs-dl > div { display: grid; grid-template-columns: minmax(7rem, auto) minmax(0, 1fr); gap: var(--space-1) var(--space-3); }
.cs-dl dt { color: var(--muted); font-size: var(--text-sm); }
.cs-dl dd { margin: 0; color: var(--text); font-size: var(--text-sm); overflow-wrap: anywhere; }
.card .t .rowmark { display: inline-flex; vertical-align: -2px; margin-left: var(--space-1); color: var(--muted); }
.newchat { max-width: 32rem; }
.newchat h2 { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-lg); }
.newchat form { display: flex; flex-wrap: wrap; align-items: end; gap: var(--space-2); }
.newchat label { flex: 1 0 100%; font-size: var(--text-sm); color: var(--muted); }
.newchat input { flex: 1 1 14rem; min-height: 44px; padding: 0 var(--space-3); border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--surface); color: var(--text); font: inherit; }
.newchat .btn { min-height: 44px; }
.newchat .ncsay { min-height: 1.4em; margin: var(--space-2) 0 0; color: var(--text); font-size: var(--text-sm); }
.newchat .nchint { margin: var(--space-2) 0 0; color: var(--muted); font-size: var(--text-sm); }
@media (max-width: 1099px) { .cs-dl > div { grid-template-columns: minmax(0, 1fr); } .cs-confirmacts .btn { min-height: 44px; } }
/* === end wac-chatactions === */

/* === wac-msgactions === (2026-10-14) the bar above the message box while replying, editing or deleting. Tokens only. */
.wac-modebar{display:flex;align-items:center;gap:8px;padding:6px 12px;border-top:1px solid var(--line);border-left:3px solid var(--accent);background:var(--surface);font-size:13px;color:var(--text)}
.wac-modebar[hidden]{display:none}
.wac-modebar .wac-modewords{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.wac-modebar[data-kind="revoke"]{border-left-color:var(--danger)}
.wac-forward{width:min(420px,calc(100vw - 32px));max-height:min(560px,calc(100vh - 64px));padding:16px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text);box-shadow:0 8px 32px rgba(16,20,28,.24)}
.wac-forward h2{margin:0 0 8px;font-size:16px;font-weight:600}
.wac-forward input[type=search]{width:100%;box-sizing:border-box;min-height:40px;padding:6px 10px;border:1px solid var(--line-strong);border-radius:6px;background:var(--surface);color:var(--text);font:inherit}
.wac-fwdlist{list-style:none;margin:8px 0;padding:0;max-height:340px;overflow:auto}
.wac-fwdlist .wac-fwdpick{width:100%;justify-content:flex-start;min-height:44px}
.wac-fwdnone{padding:8px;color:var(--muted)}

/* === wac-money === the Revenue view (2026-10-14, `WAC_MONEY`): tokens only, no inline style anywhere. */
.money{display:flex;flex-direction:column;gap:12px;min-width:0;max-width:100%}
.money>*,.mgroup>*{min-width:0;max-width:100%}
.mcharts{min-width:0}
.mchart{min-width:0;overflow:hidden}
.msummary{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(160px,100%),1fr));gap:8px}
.mbox{border:1px solid var(--line);border-radius:var(--radius-md);background:var(--surface);padding:8px 12px}
.mbox h2{font-size:var(--text-sm);font-weight:600;color:var(--muted);margin:0 0 4px}
.mbox ul{list-style:none;margin:0;padding:0;font-size:var(--text-lg);font-weight:600}
.mbox p{margin:0;font-size:var(--text-lg);font-weight:600}
.mbox.in{border-left:3px solid var(--accent)}
.mbox.out{border-left:3px solid var(--warning-line)}
.mbox .mnone{font-size:var(--text-base);font-weight:400;color:var(--muted)}
.mtools{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end}
.mf{display:flex;flex-direction:column;font-size:var(--text-xs);color:var(--muted);gap:2px}
.mf select,.mf input{min-height:36px;padding:4px 8px;border:1px solid var(--line-strong);border-radius:var(--radius-sm);background:var(--surface);color:var(--text);font:inherit;font-size:var(--text-base)}
.msearch{flex:1 1 160px}
/* The column widths mean something only in a fixed layout (using-table-editor: "table-layout:fixed IS WHAT MAKES AddColumn's
   WIDTHS MEAN ANYTHING"); the row number is the grid's own column and gets its width here. */
#wac-money .mtablehost table.kEditableTable{table-layout:fixed;width:100%}
/* One line per cell (the grid's rows have a fixed height, so a wrapped second line would be hidden); a description
   longer than its column ends in an ellipsis and its cell's `title` holds the whole of it (`AppMoneyTable.MarkFixed`). */
#wac-money .mtablehost table.kEditableTable{font-size:var(--text-sm)}
#wac-money .mtablehost table.kEditableTable td,#wac-money .mtablehost table.kEditableTable th{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:0}
#wac-money .mtablehost table.kEditableTable .mrow{width:32px !important;min-width:32px !important;max-width:32px !important}
#wac-money .mtablehost table.kEditableTable .mslack{width:auto !important}
/* Below the panes the row is wider than the screen (it scrolls inside its box): with no room left over an auto column
   would get none, so the description column has a real width there. */
@media (max-width: 1099px){#wac-money .mtablehost table.kEditableTable .mslack{width:120px !important}}
.mtablehost td.kEditableTableAddHint:empty::before{content:attr(data-addhint);color:var(--muted);font-style:italic}
.mtablehost td.mfixed{color:var(--muted)}
.mtablehost td.minvalid{outline:2px solid var(--danger);outline-offset:-2px}
.mtablehost a.mopen{display:inline-flex;align-items:center;min-height:32px;padding:0 6px;color:inherit;font-weight:600;text-decoration:underline}
#wac-money .mtablehost td select.kField{border:1px solid var(--text) !important;border-radius:var(--radius-sm) !important}
.mcharts{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(280px,100%),1fr));gap:8px}
.mchart{margin:0;border:1px solid var(--line);border-radius:var(--radius-md);background:var(--surface);padding:8px}
.mchart svg{width:100%;height:auto;display:block}
.mchartt{font-size:13px;font-weight:600;color:var(--text);margin:0 0 4px}
.mnote{margin:0;min-height:1.2em;color:var(--text);font-size:var(--text-sm)}
.mgroup h2{font-size:var(--text-md);font-weight:600;margin:8px 0 4px}
.mgroup.overdue h2{color:var(--warning-ink)}
.mcount{color:var(--muted);font-weight:400}
.mtablewrap{position:relative;overflow-x:auto;border:1px solid var(--line);border-radius:var(--radius-md);background:var(--surface)}
.mtable{width:100%;border-collapse:collapse;font-size:var(--text-sm)}
.mtable th{text-align:left;font-weight:600;color:var(--muted);padding:6px 8px;border-bottom:1px solid var(--line);white-space:nowrap}
.mtable td{padding:6px 8px;border-bottom:1px solid var(--line);vertical-align:middle}
.mtable tr:last-child td{border-bottom:0}
.mtable .mc-amount,.mtable .mc-age{text-align:right;white-space:nowrap}
.mtable tr.overdue .mc-state{color:var(--warning-ink);font-weight:600}
.mtable tr.settled{color:var(--muted)}
.mcellbtn{background:none;border:1px dashed transparent;border-radius:var(--radius-sm);color:inherit;font:inherit;padding:4px 6px;min-height:32px;cursor:pointer}
.mcellbtn:hover,.mcellbtn:focus-visible{border-color:var(--line-strong)}
.mcellin{min-height:32px;width:9em;padding:2px 6px;border:1px solid var(--accent);border-radius:var(--radius-sm);background:var(--surface);color:var(--text);font:inherit}
.mempty{color:var(--muted)}
.mtoday{border:1px solid var(--warning-line);border-radius:var(--radius-md);background:var(--warning-soft);padding:6px 12px;margin:8px 0}
.mtoday h2{font-size:var(--text-sm);margin:0 0 4px}
.mtoday ul{margin:0;padding-left:18px}
.mtoday a{color:var(--text)}
@media (max-width: 1099px){.mtable .mc-date,.mtable .mc-state{display:none}.mcellbtn{min-height:44px}}
.mchart{color:var(--text)}
.mchart svg{fill:currentColor}
.mchart .c1{fill:var(--viz1)}
.mchart .c2{fill:var(--viz2)}
.mchart text,.mchart tspan,.mchart text[class],.mchart tspan[class]{fill:currentColor;stroke:none}
.mchart .muted,.mchart text.muted{fill:var(--muted)}
.mchart line.muted,.mchart polyline.muted,.mchart path.muted[fill="none"]{fill:none;stroke:var(--muted)}
.money input:focus-visible,.money select:focus-visible,.money summary:focus-visible,.money button:focus-visible,.money a:focus-visible,.mtoday a:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
@media (max-width: 1099px){
.mf select,.mf input{min-height:44px}
.mtablehost a.mopen{min-height:44px;min-width:44px}
#wac-money .mtablehost td select.kField{min-height:44px !important}
.mf{flex:1 1 140px;min-width:0}
.mtable td a,.mpeople td a{display:inline-flex;align-items:center;min-height:44px;min-width:44px}
.mtoday li a{display:inline-flex;align-items:center;min-height:44px}
.mtoday h2 a{display:inline-flex;align-items:center;min-height:44px;min-width:44px}
.mcellbtn,.mcellin{min-height:44px;min-width:44px}
}
.money input:focus,.money input:focus-within{outline:2px solid var(--accent);outline-offset:2px}

/* === wac-searchacts ===
   Search hits grouped per contact (`AppSearchActions`): a head (disc, name, count, Open chat, Chat settings) and,
   on each hit, a row of icon buttons (Reply, React + the six faces, Help me reply, Forward, Remind me, Copy text)
   with the hit's own status line. Tokens only; no inline style. */
.sgroups { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.sgroup { border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--bg); }
.sghead { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--line); }
.sghead > .av { width: 28px; height: 28px; font-size: var(--text-xs); flex: none; }
.sgname { font-weight: 600; overflow-wrap: anywhere; }
.sgcount { font-size: var(--text-xs); color: var(--dim); }
.sgacts { margin-left: auto; display: inline-flex; align-items: center; gap: var(--space-1); }
.sgroup .hits { gap: 0; }
.sgroup .hit { border-top: 1px solid var(--line); }
.sgroup .hit:first-child { border-top: 0; }
.sgroup .hit a.hitopen, .sgroup .hit button.hitopen { display: block; width: 100%; text-align: left; font: inherit; color: inherit;
    background: none; border: 0; border-radius: 0; padding: var(--space-2) var(--space-3) var(--space-1); text-decoration: none; cursor: pointer; }
.sgroup .hit a.hitopen:hover, .sgroup .hit button.hitopen:hover { background: var(--soft); }
.sacts { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); padding: 0 var(--space-2) var(--space-2); }
.sacts .saact, .sacts .saface { color: var(--dim); }
.sacts .saact:hover, .sacts .saact[aria-expanded="true"] { color: var(--ink); }
.safaces { display: inline-flex; flex-wrap: wrap; gap: var(--space-1); }
.safaces[hidden] { display: none; }
.sasay { font-size: var(--text-xs); color: var(--ink); flex-basis: 100%; padding-left: var(--space-1); }
.sasay:empty { display: none; }
@media (pointer: coarse), (max-width: 1099px) {
    .sacts .saact, .sacts .saface, .sgacts .btn { min-width: 44px; min-height: 44px; }
}

/* === wac-taskdone === (owner, 2026-10-22: closed tasks folded, each with "Reopen") */
.task .treopen { flex: none; align-self: center; margin-left: var(--space-2, 8px); }
.task.topen .treopen { position: relative; z-index: 2; }
@media (pointer: coarse), (max-width: 1099px) {
    .task .treopen { min-height: 44px; }
}

/* === wac-demo === DEMO MODE choices (AppDemo.MenuPicks) in the settings menu, under the demo switch. */
.demopicks { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-2) var(--space-3); }
.demopicks select { width: 100%; min-height: 32px; font: inherit; }
