  /* Keyboard focus is visible everywhere, in the accent, and only for
     keyboard focus — a mouse click does not draw a ring. */
  :focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
  .field input:focus-visible, .field textarea:focus-visible,
  #search:focus-visible, #nc-input:focus-visible, #text:focus-visible,
  .dial-num:focus-visible { outline: none; }

  * { box-sizing: border-box; }
  html, body { height: 100%; }
  body {
    margin: 0;
    font-family: var(--ui);
    font-size: 14px;
    color: var(--text);
    background: var(--bg);
    background-image: var(--scene);
    background-attachment: fixed;
    -webkit-font-smoothing: antialiased;
  }
  button, input, textarea, select { font: inherit; color: inherit; }
  svg { width: 24px; height: 24px; fill: currentColor; display: block; flex: none; }
  .hidden { display: none !important; }

  /* ---------- sign in ---------- */
  #signin, #link, #devices {
    min-height: 100%;
    display: grid;
    place-items: center;
    padding: 24px;
    background: var(--surface-2);
  }
  /* The device-link approval: the card's words and its one code field. */
  .link-who { margin: 0 0 20px; color: var(--text-2); line-height: 1.45; }
  .link-actions { display: flex; align-items: center; gap: 12px; }
  #link-code { text-transform: uppercase; letter-spacing: 2px; font-family: var(--mono, monospace); }
  .card {
    width: 100%;
    max-width: 420px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 40px;
  }
  .card h1 {
    margin: 0 0 8px;
    font-size: 24px;
    font-weight: 400;
    letter-spacing: 0;
  }
  .card p.sub { margin: 0 0 28px; color: var(--text-2); }
  .field { position: relative; margin-bottom: 20px; }
  .field input {
    width: 100%;
    padding: 13px 15px;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 4px;
    outline: none;
  }
  .field input:focus { border-color: var(--primary); box-shadow: inset 0 0 0 1px var(--primary); }
  .field label {
    position: absolute;
    left: 8px; top: -8px;
    padding: 0 6px;
    font-size: 12px;
    color: var(--text-2);
    background: var(--surface);
  }
  .field input:focus + label { color: var(--primary); }

  .btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 36px;
    padding: 0 24px;
    border: 0;
    /* A pill, like every button on the phone: Android passes no `shape`,
       so Material3's default applies, which is Radius.pill (Design.kt:167)
       — and the web app's own accent controls are already round (the FAB,
       the dial button, the nav's lit door). 4px made .btn the one square
       control in either client. The height is fixed at 36px and nothing
       wearing .btn wraps, so the radius has nothing to fight. */
    border-radius: 999px;
    background: var(--primary);
    color: var(--on-primary);
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
  }
  .btn:hover:not(:disabled) { filter: brightness(1.06); box-shadow: var(--shadow); }
  .btn:disabled { opacity: .5; cursor: default; }
  .btn.text { background: transparent; color: var(--primary); padding: 0 12px; }
  .btn.text:hover:not(:disabled) { background: var(--hover); box-shadow: none; filter: none; }
  .btn.outline {
    background: transparent; color: var(--primary);
    border: 1px solid var(--border);
  }
  .btn.outline:hover:not(:disabled) { background: var(--hover); box-shadow: none; filter: none; }
  .btn.danger { background: transparent; color: var(--danger); border: 1px solid var(--border); }
  .btn.danger:hover:not(:disabled) { background: var(--hover); box-shadow: none; filter: none; }

  .notice {
    margin: 0 0 20px;
    padding: 12px 14px;
    border-radius: 4px;
    background: var(--surface-2);
    color: var(--danger);
    font-size: 13px;
  }
  .notice.ok { color: var(--text); }

  /* ---------- shell ---------- */
  #app { height: 100%; display: flex; flex-direction: column; }

  header.bar {
    flex: none;
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px; padding: 12px 16px 6px;
    background: transparent;   /* the scene shows through, like the app */
    position: relative;        /* the account menu + search hang off this */
  }
  /* The active-line chip — the design's "MAIN · 415 555 0142 ▾". */
  .linechip {
    display: inline-flex; align-items: center; gap: 9px; min-width: 0;
    background: var(--glass-2); border: 1px solid var(--border);
    border-radius: 999px; padding: 8px 12px; cursor: pointer; color: var(--text);
  }
  .linechip .ld { width: 7px; height: 7px; border-radius: 50%;
                  background: var(--primary); flex: none; }
  .linechip #linechip-num {
    font: 500 12px var(--mono); letter-spacing: .04em; color: var(--text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* Do Not Disturb, said out loud wherever it applies. */
  /* `StatusChip("Do Not Disturb", tint = web().missed)` — the app's pill:
     the tint at 16% behind it, the tint at 35% around it, the words in
     the tint, mono and in caps (Glass.kt:93-105). It used to be the
     note amber, which is the colour this app uses for something worth
     reading; a line that is refusing calls is the missed red. */
  .dndchip {
    display: inline-flex; align-items: center; gap: 6px; flex: none;
    padding: 5px 10px; border-radius: 999px;
    background: color-mix(in srgb, var(--missed) 16%, transparent);
    border: 1px solid color-mix(in srgb, var(--missed) 35%, transparent);
    color: var(--missed);
    font: 600 11px var(--mono); letter-spacing: .06em; text-transform: uppercase;
    white-space: nowrap;
  }
  .dndchip svg { width: 13px; height: 13px; fill: currentColor; }
  header.bar .dndchip { margin-left: -2px; }
  .pane-tools { display: inline-flex; align-items: center; gap: 8px; }
  .linechip .lcx { width: 14px; height: 14px; fill: var(--text-2); flex: none; }
  .bar-right { display: flex; align-items: center; gap: 4px; flex: none; }
  /* Search is kept but tucked behind an icon; tapping it slides a field
     over the bar. */
  .searchfield { display: none; }
  body.searching .searchfield {
    display: flex; align-items: center; gap: 8px;
    position: absolute; left: 12px; right: 12px; top: 8px; z-index: 6;
    height: 48px; padding: 0 6px 0 14px;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: 999px;
  }
  body.searching .linechip, body.searching .bar-right { visibility: hidden; }
  .searchfield > svg { flex: none; width: 20px; height: 20px; fill: var(--text-2); }
  .searchfield input {
    flex: 1; min-width: 0; border: 0; background: transparent; outline: none;
    color: var(--text); font-size: 15px;
  }
  .searchfield input::placeholder { color: var(--text-2); }
  #search-clear { width: 34px; height: 34px; flex: none; }
  #search-clear svg { width: 18px; height: 18px; }

  /* ---------- the account menu ---------- */
  .menu.acct { top: 60px; right: 12px; min-width: 260px; }
  .acct-head {
    display: flex; gap: 12px; align-items: center;
    padding: 14px 18px 12px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 6px;
  }
  .acct-head .face {
    width: 40px; height: 40px; border-radius: 50%; flex: none;
    background: var(--primary); color: var(--on-primary);
    display: grid; place-items: center; font-weight: 500; font-size: 17px;
  }
  .acct-head .whoami { min-width: 0; }
  .acct-head .whoami b {
    display: block; font-weight: 500; font-size: 14px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .acct-head .whoami span { font-size: 12.5px; color: var(--text-2); }
  .bar .spacer { flex: 1; }
  .icon-btn {
    display: grid; place-items: center;
    width: 40px; height: 40px;
    border: 0; border-radius: 50%;
    background: transparent; color: var(--text-2);
    cursor: pointer;
    transition: transform .12s ease, color .12s ease;
  }
  .icon-btn:hover { background: var(--hover); }
  .avatar-btn {
    width: 32px; height: 32px; border-radius: 50%; border: 0;
    background: var(--primary); color: var(--on-primary);
    font-weight: 500; cursor: pointer;
  }

  .body { flex: 1; display: flex; min-height: 0; }

  /* Four labeled doors, matching Android HomeNav. */
  nav.bottomnav {
    display: flex; gap: 4px; padding: 6px;
    border-radius: 999px; background: var(--nav-surface);
    border: 1px solid var(--border); box-shadow: var(--shadow);
  }
  nav.bottomnav button {
    flex: 1; min-width: 0; min-height: 58px; padding: 7px 4px;
    display: flex; flex-direction: column; gap: 3px;
    align-items: center; justify-content: center;
    border: 0; background: transparent; color: var(--text-2);
    cursor: pointer; border-radius: 999px;
    transition: background .15s ease, color .15s ease;
  }
  nav.bottomnav .pill { position: relative; display: grid; place-items: center; }
  nav.bottomnav svg { width: 24px; height: 24px; fill: currentColor; }
  nav.bottomnav .nav-label { font-size: 11px; }
  nav.bottomnav button:hover { background: var(--hover); color: var(--text); }
  /* The door you are on wears the ACCENT, filled — HomeNav.kt, which
     measured all three candidates against the bar the door sits in
     (--nav-surface), because that is the surround WCAG 1.4.11 means when
     it asks 3:1 of a control you have to be able to pick out:

         the ink at 12%         1.41:1 night   1.28:1 day    under the bar
         white / near-black    16.14:1        17.84:1        the maximum
         the accent, filled     8.45:1         4.98:1        this

     This rule WAS "the ink at 12%" — the option that comment rejects by
     name, and the one that fails. The accent also carries 8.71:1 and
     4.98:1 on its own ink, past the 4.5:1 1.4.3 asks of the label. */
  nav.bottomnav button[aria-selected="true"] {
    background: var(--primary);
    color: var(--on-primary); font-weight: 600;
  }
  .navbadge {
    position: absolute; top: -2px; right: 6px;
    min-width: 15px; height: 15px; padding: 0 3px;
    border-radius: 8px;
    /* web.missed, which is the token HomeNav hands the Recents door —
       not --danger. They are neighbours, not the same colour. */
    background: var(--missed); color: #fff;
    font-size: 9.5px; font-weight: 700; line-height: 15px;
    font-family: var(--mono);
  }
  /* Messages counts in the ACCENT, Recents in missed-red — HomeNav.kt
     passes a different colour to each door — and the digits are WHITE on
     both (`color = Color.White`, HomeNav.kt:292), never the accent's own
     ink. */
  nav.bottomnav #badge-unread { background: var(--primary); color: #fff; }
  /* A door you are STANDING ON does not also wear its count: the screen
     behind it is the answer. `if (badge > 0 && !active)`, HomeNav.kt. */
  nav.bottomnav button[aria-selected="true"] .navbadge,
  nav.bottomnav button[aria-pressed="true"] .navbadge { display: none; }

  /* ---------- conversation list ---------- */
  section.list {
    width: var(--list); flex: none;
    border-right: 1px solid var(--border);
    display: flex; flex-direction: column;
    background: var(--surface);
    min-height: 0;
  }
  .list-head {
    flex: none;
    display: flex; align-items: baseline; justify-content: space-between;
    padding: 14px 18px 8px;
  }
  /* NO PAGE TITLE. The app names no screen: the lit nav door is what
     says where you are, on the phone and on the tablet rail alike (the
     tablet golden opens straight onto TODAY). The name is kept in the
     DOM for the screen reader and for the document title, and taken out
     of the picture. */
  .list-head > span:first-child, .pane-head > span:first-child {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  /* The team lenses — All, Mine, Unassigned, Done — as chips above the
     list, left-aligned, with a count where there is one. */
  .list-head .tabs { margin-left: 0; flex-wrap: wrap; }
  .list-head .tabs button { font-weight: 600; font-family: var(--ui); padding: 5px 11px;
                            border-color: transparent; background: var(--glass); }
  .list-head .tabs button[aria-selected="true"] { background: var(--selected); color: var(--selected-text); }
  .list-head .tabs .n { margin-left: 5px; opacity: .75; font-variant-numeric: tabular-nums; }
  .rows {
    flex: 1; overflow-y: auto;
    padding: 0 0 14px;
    display: flex; flex-direction: column; gap: 0;
  }
  /* Every list is one sheet with hairlines between its rows, as the app
     draws them (Glass.kt: "a page is one sheet from the top of the
     screen down, hairlines between rows"): nothing behind a row, the
     divider inset to where the words begin — 20px in, a 40px avatar,
     a 13px gap — and the row's own hover and selection laid over the
     sheet edge to edge. */
  .row {
    display: flex; gap: 13px; align-items: center;
    /* 8px, not 20: this is the row's padding INSIDE its card, not a page
       margin, and 20 put the avatar 36px from the card's edge (20 + the
       12px reserved dot column) while the trailing side ended 12px from
       the other one. Every row sat right of where it looked like it
       should, with an empty channel down the inside of every card, and
       the 16px went to the words instead (ListRow.padH). */
    padding: 12px 12px 12px 8px;
    cursor: pointer; position: relative;
    border: 0; width: 100%; text-align: left;
    background: transparent; border-radius: 0;
  }
  .row::after, .callrow::after, .contactrow::after, .vmrow::after {
    content: ""; position: absolute; left: 73px; right: 20px; bottom: 0;
    height: 1px; background: var(--border); pointer-events: none;
  }
  .row:hover { background: var(--hover); }
  .row[aria-selected="true"] { background: var(--selected); }
  .row[aria-selected="true"] .who { color: var(--selected-text); }
  .avatar {
    width: 40px; height: 40px; flex: none;
    border-radius: 50%;
    display: grid; place-items: center;
    color: #fff;
  }
  /* In the design's message/contact lists the avatar is a rounded square. */
  .row .avatar { border-radius: 14px; }
  .avatar svg { width: 22px; height: 22px; }
  .row .mid { flex: 1; min-width: 0; }
  .who {
    font: 600 15px var(--ui); color: var(--text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* A row's name and the marks beside it sit on ONE line, the name
     taking the ellipsis — Android's Row in ThreadRow. */
  .row .who { display: flex; align-items: center; min-width: 0; }
  .row .who .who-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  .row .who .bell, .row .who .pinned, .row .who .nap-due { flex: none; }
  .snippet {
    color: var(--text-2); font-size: 13px; margin-top: 4px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .row .when {
    flex: none;
    display: flex; flex-direction: column; align-items: flex-end; gap: 7px;
    font: 500 11px var(--mono); color: var(--text-2);
  }
  .row .when span, .row .when time { font: 500 11px var(--mono); color: var(--text-2); }
  .row.unread .who { font-weight: 700; color: var(--text); }
  .row.unread .snippet { color: var(--text); font-weight: 700; }
  .row.unread .when span { color: var(--primary); }
  .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--primary); }
  /* The unread mark's own column, ahead of the avatar — Android's
     ListRow.dotColumn (16dp) with ListRow.dot (8dp) centred in it. It is
     in the row whether or not there is a dot, so an unread row and a
     read one line up. */
  .row .leaddot, .vm-head .leaddot, .call-head .leaddot {
    flex: none; width: 12px; display: grid; place-items: center;
  }
  /* Recents' mark is the missed red, not the accent: what is unread there
     is a call that did not get through. */
  .dot.missed { background: var(--missed); }
  /* A voicemail's time, in the trailing column a conversation's sits in
     — mono, and the accent while it is unheard (VoicemailScreen.kt:573). */
  .vm-head .vm-at {
    flex: none; margin-left: 8px;
    font: 500 11px var(--mono); color: var(--text-2);
  }
  .vm-head .vm-at.unheard { color: var(--primary); }

  /* "TODAY", "YESTERDAY", a letter: the quiet caps that break a list
     into days, as the app's SceneLabel does. */
  .section, .daylabel, .letterhead {
    padding: 14px 20px 4px;
    font: 600 11px var(--ui); letter-spacing: 1.2px;
    text-transform: uppercase; color: var(--text-2);
  }
  /* "There is nothing here", the one way — Android's NothingHere
     (android/.../ui/Glass.kt:237), measure for measure: a 72px disc
     holding the screen's icon at 30px, then the headline, then a
     sentence, then the move to make next.

     Before this there were three of these in the web app and none of
     them was this: Messages drew an icon and one line, and Recents and
     Contacts reused .daylabel — the DAY SEPARATOR style — so an empty
     Recents said "NO CALLS YET." in the same small caps as "YESTERDAY",
     styling the only thing on the screen as a header for a section that
     was not there. --circle and --faint are shared with Android through
     design/tokens.json, so the disc and its icon are not a second
     opinion of the same two colours. */
  .nothing {
    flex: 1; min-height: 0;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    padding: 0 40px 60px; text-align: center;
  }
  .nothing .disc {
    width: 72px; height: 72px; border-radius: 50%;
    background: var(--circle);
    display: grid; place-items: center;
  }
  .nothing .disc svg { width: 30px; height: 30px; color: var(--faint); }
  /* 17/24 semibold, then 13/18 — Type.headline and Type.meta. */
  .nothing b {
    margin-top: 20px; font-size: 17px; line-height: 24px; font-weight: 600;
    color: var(--text);
  }
  .nothing span {
    margin-top: 8px; font-size: 13px; line-height: 18px; color: var(--text-2);
    max-width: 34ch;
  }
  /* The pill Android puts under the sentence: 24px above it, 24/12
     inside, 12/16 medium on the accent. */
  .nothing button {
    margin-top: 24px; border: 0; cursor: pointer;
    padding: 12px 24px; border-radius: 999px;
    background: var(--primary); color: var(--on-primary);
    font: 500 12px/16px var(--ui);
  }

  .empty {
    padding: 48px 24px; text-align: center; color: var(--text-2);
  }
  .empty svg { width: 48px; height: 48px; margin: 0 auto 16px; opacity: .5; }

  /* A pencil on a circle, and nothing else. Every phone's is, and the
     word beside it was doing no work the icon was not already doing. */
  .fab {
    position: absolute; right: 20px; bottom: 20px;
    display: grid; place-items: center;
    width: 56px; height: 56px; padding: 0;
    border: 0; border-radius: 18px;
    background: var(--primary); color: var(--on-primary);
    box-shadow: var(--shadow); cursor: pointer;
  }
  .fab svg { width: 24px; height: 24px; }

  /* ---------- new chat ----------
     Android's New conversation screen, and not a dialog. A dialog asked
     for the recipient AND the message before it would do anything, which
     makes it a form: you could not look up who you meant, could not
     change your mind about them, and cancelling threw away what you had
     already typed. This is a screen. It picks a person, then opens the
     conversation, and the message is written in the composer like every
     other message in the app. */
  section.newchat {
    width: var(--list); flex: none;
    border-right: 1px solid var(--border);
    display: flex; flex-direction: column;
    background: var(--surface);
    min-width: 0; min-height: 0;
  }
  .nc-head {
    height: 64px; flex: none;
    display: flex; align-items: center; gap: 8px;
    padding: 0 8px 0 6px;
  }
  .nc-head .title { font-size: 20px; font-weight: 400; }
  .nc-row {
    display: flex; align-items: center; gap: 10px;
    padding: 6px 20px 14px;
    border-bottom: 1px solid var(--border);
  }
  .nc-row + .nc-row { padding-top: 12px; }
  .nc-row .nc-label { flex: none; color: var(--text-2); font-size: 15px; }
  /* The field is the row, with no box drawn round it — the divider under
     it is the whole decoration, the way the phone does it. */
  #nc-input, #nc-from {
    flex: 1; min-width: 0; height: 34px;
    border: 0; outline: none; background: transparent;
    color: var(--text); font: inherit; font-size: 15px;
  }
  #nc-from { cursor: pointer; }
  /* The list it opens is the browser's own, drawn on the page's surface
     rather than the system's white — the page's text colour on white
     was unreadable in the dark theme. */
  #nc-from option { background: var(--surface); color: var(--text); }
  #nc-from option:disabled { color: var(--text-2); }
  body.composing section.list { display: none; }
  .nc-results { flex: 1; overflow-y: auto; padding-bottom: 12px; }
  /* A conversation row is two lines against a 40px avatar, so the list
     aligns to the top. Half of these are one line — a bare number nobody
     has named — and top-aligning those hangs the text off the avatar's
     forehead. */
  .nc-results .row { align-items: center; }
  .nc-hint { padding: 32px 24px; text-align: center; color: var(--text-2); font-size: 13px; }
  /* The one row that is not a person: "Send to (845) 746-2044", for
     someone who is in no list because you have never texted them. */
  .row .avatar.plain { background: var(--hover) !important; color: var(--text-2); }

  /* ---------- thread ---------- */
  section.thread { flex: 1; display: flex; flex-direction: column; min-width: 0;
                   position: relative; }
  /* A file held over the conversation is an attachment about to happen. */
  section.thread.dropping::after {
    content: ""; position: absolute; inset: 8px; border-radius: 16px;
    border: 2px dashed var(--primary); pointer-events: none;
  }
  /* Something arrived below what you are reading. */
  .jump {
    position: absolute; left: 50%; transform: translateX(-50%);
    bottom: 84px; z-index: 5;
    border: 0; border-radius: 999px; padding: 8px 16px;
    background: var(--primary); color: var(--on-primary);
    font: inherit; font-size: 13px; font-weight: 500;
    box-shadow: 0 2px 8px rgba(0,0,0,.28); cursor: pointer;
  }
  /* Links inside a bubble write in the bubble's own ink — a blue link on
     the blue outbound bubble would vanish. The underline carries it. */
  .bubble a { color: inherit; text-decoration: underline; }
  /* What is written but not sent, on the conversation it belongs to. */
  .snippet .dmark { color: var(--danger); font-weight: 500; }
  .thread-head {
    height: 64px; flex: none; position: relative;
    display: flex; align-items: center; gap: 14px;
    padding: 0 16px;
    border-bottom: 1px solid var(--border);
  }
  .thread-head .back { display: none; }
  .thread-head .avatar { width: 36px; height: 36px; border-radius: 12px; }
  .thread-head .title { font: 600 15px var(--ui); color: var(--text); }
  .thread-head .via {
    font: 400 10.5px var(--mono); letter-spacing: .06em;
    text-transform: uppercase; color: var(--text-2); margin-top: 3px;
  }
  #thread-call { background: var(--selected); color: var(--primary); }

  .messages {
    flex: 1; overflow-y: auto; overflow-x: hidden;
    padding: 20px 24px 8px;
    display: flex; flex-direction: column; gap: 2px;
  }
  /* "Yesterday · 4:23 PM", centred. A conversation is read in bursts
     minutes or months apart, and the useful question at the top of one is
     not which day it was but how long it had been. */
  .daysep {
    align-self: center; margin: 22px 0 14px;
    font-size: 12px; font-weight: 500; color: var(--text-2);
  }
  .daysep:first-child { margin-top: 8px; }
  .msg {
    max-width: min(65%, 560px); position: relative;
    display: flex; flex-direction: column;
  }
  .msg.in { align-self: flex-start; align-items: flex-start; }
  .msg.out { align-self: flex-end; align-items: flex-end; }
  .bubble {
    padding: 10px 16px;
    border-radius: 20px;
    font-size: 15px; line-height: 1.35;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
  }
  .msg.in .bubble { background: var(--bubble-in); color: var(--bubble-in-text); }
  .msg.out .bubble { background: var(--primary); color: var(--on-primary); }
  /* Consecutive bubbles from the same side tuck together, the way every
     messaging app groups a burst into one visual block. */
  .msg.in.mid .bubble, .msg.in.last .bubble { border-top-left-radius: 4px; }
  .msg.in.first .bubble, .msg.in.mid .bubble { border-bottom-left-radius: 4px; }
  .msg.out.mid .bubble, .msg.out.last .bubble { border-top-right-radius: 4px; }
  .msg.out.first .bubble, .msg.out.mid .bubble { border-bottom-right-radius: 4px; }
  .msg + .msg.first { margin-top: 10px; }
  .meta { font-size: 11px; color: var(--text-2); margin: 4px 2px 6px; }
  /* Who is on a conversation, in the Add people dialog. */
  .people-list { display: flex; flex-direction: column; gap: 4px; margin: 4px 0 16px; }
  .people-row { display: flex; align-items: center; justify-content: space-between;
    padding: 6px 4px 6px 12px; border: 1px solid var(--border); border-radius: 8px; }
  .dlg-hint { font-size: 12px; color: var(--text-2); margin: 0 0 12px; }
  /* A group member's name over the first of their bubbles. */
  .msg.in > .who { font-size: 11px; color: var(--text-2); margin: 6px 12px 2px; }
  .meta.bad { color: var(--danger); }
  .bubble.media { padding: 4px; background: transparent !important; }
  /* A measured picture's bubble IS its final shape before the bytes
     arrive: the skeleton shimmers in that exact footprint, and the
     image fades into it without moving a thing. */
  .bubble.media.sized { overflow: hidden; }
  .bubble.media.sized img {
    width: 100%; height: 100%; object-fit: cover; display: block;
    opacity: 1; transition: opacity .25s ease;
  }
  .bubble.media.skeleton { border-radius: 14px; }
  .bubble.media.skeleton img { opacity: 0; }
  .bubble.media.skeleton::before {
    content: ""; position: absolute; inset: 0; border-radius: 14px;
    background: linear-gradient(100deg, var(--bubble-in) 40%,
      rgba(255,255,255,.14) 50%, var(--bubble-in) 60%) 0 0 / 200% 100%;
    animation: shimmer 1.1s linear infinite;
  }
  @keyframes shimmer { to { background-position: -200% 0; } }
  .bubble.media.sized { position: relative; }
  .bubble.media img {
    display: block; max-width: 280px; max-height: 320px;
    border-radius: 14px; cursor: pointer;
  }
  .bubble.attach {
    display: inline-flex; align-items: center; gap: 8px;
    text-decoration: none;
  }
  .bubble.media audio { display: block; max-width: 280px; }

  /* ---------- a voice note ----------
     WhatsApp's, because it is the one everybody has already used ten
     thousand times: a bare play triangle, a waveform with a bead on it
     you can drag, the length under the triangle and the time at the far
     end, and a face with a microphone on it so the bubble says what it is
     before you have read a thing. The browser's own <audio controls> is a
     grey slab with a volume slider and an overflow menu — furniture from
     a media player, in the middle of a conversation. */
  .bubble.voice { padding: 8px 12px 6px 8px; }
  .vn {
    display: grid; column-gap: 8px; align-items: center;
    /* Definite, so the waveform column has something to be the rest OF.
       A percentage here resolves against a bubble that is itself sizing
       to this grid, and the waveform collapses to nothing. */
    width: 300px; max-width: 100%;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "play wave face"
                         "len  when face";
  }
  /* Ours reads the other way round, as it does on the phone. */
  .msg.out .vn {
    grid-template-columns: auto auto minmax(0, 1fr);
    grid-template-areas: "face play wave"
                         "face len  when";
  }
  .vn-play {
    grid-area: play; width: 30px; height: 30px; padding: 0;
    border: 0; background: transparent; color: inherit;
    display: grid; place-items: center; cursor: pointer; opacity: .8;
  }
  .vn-play svg { width: 26px; height: 26px; }
  .vn-wave {
    grid-area: wave; position: relative;
    height: 32px; cursor: pointer; touch-action: none;
    display: flex; align-items: center; gap: 2px;
  }
  .vn-wave .bar {
    flex: 1; min-width: 0; border-radius: 2px;
    background: currentColor; opacity: .35;
  }
  .vn-wave .bar.on { opacity: .95; }
  /* The bead. Where you are, and the thing you drag to be somewhere else. */
  .vn-head {
    position: absolute; top: 50%; left: 0;
    width: 12px; height: 12px; margin: -6px 0 0 -6px;
    border-radius: 50%; pointer-events: none;
  }
  .msg.in .vn-head { background: var(--primary); }
  .msg.out .vn-head { background: var(--on-primary); }
  .vn-len, .vn-when {
    font-size: 11px; opacity: .75; font-variant-numeric: tabular-nums;
    line-height: 1.6;
  }
  .vn-len { grid-area: len; }
  .vn-when { grid-area: when; justify-self: end; }
  .vn-face { grid-area: face; position: relative; width: 40px; height: 40px; }
  .vn-face .avatar { width: 40px; height: 40px; }
  /* Nobody sent this from a face we have a picture of — but the shape is
     half of what makes the bubble recognisable, so it keeps the shape. */
  .vn-face .avatar.ours {
    background: rgba(128, 128, 128, .28);
    /* A wash of whatever the bubble writes in, so it reads on a light
       bubble and a dark one without either being named here. */
    background: color-mix(in srgb, currentColor 22%, transparent);
  }
  .vn-face .mic {
    position: absolute; right: -3px; bottom: -1px;
    width: 18px; height: 18px; border-radius: 50%;
    display: grid; place-items: center;
  }
  .vn-face .mic svg { width: 12px; height: 12px; }
  .msg.in .vn-face .mic { background: var(--primary); color: var(--on-primary); }
  .msg.out .vn-face .mic { background: var(--on-primary); color: var(--primary); }
  .bubble.media video { display: block; max-width: 280px; border-radius: 14px; }

  .composer { flex: none; padding: 12px 24px 20px; position: relative; }
  .pending { padding-left: 84px; }

  .picker { max-width: 520px; }
  .picker .list { max-height: 46vh; overflow-y: auto; margin: 0 -24px; }
  .picker .item {
    display: block; width: 100%; text-align: left;
    padding: 12px 24px; border: 0; background: transparent; cursor: pointer;
  }
  .picker .item:hover { background: var(--hover); }
  .picker .item b { display: block; font-weight: 500; }
  .picker .item span {
    display: block; color: var(--text-2); font-size: 13px; margin-top: 2px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .picker .item .drop {
    float: right; color: var(--text-2); font-size: 12px;
    border: 0; background: transparent; cursor: pointer;
  }

  .headtext { flex: 1; min-width: 0; }
  /* One line each. The header used to carry a wordy assignment dropdown
     beside the name, which squeezed a plain phone number onto two lines
     and left the header looking broken before you had read a word of the
     conversation. Everything that was in there is behind the ⋮ now. */
  .thread-head .title, .thread-head .via {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* ---------- picking messages out ----------
     Hold one and the conversation becomes a list you are choosing from:
     the header turns into a count and the two things worth doing to a
     message, and everything in a bubble stops being a control. */
  .selbar {
    position: absolute; inset: 0; z-index: 5;
    display: flex; align-items: center; gap: 4px;
    padding: 0 8px 0 6px;
    background: var(--surface);
  }
  .selcount { flex: 1; min-width: 0; font-size: 18px; font-weight: 500; }
  /* The tint runs the full width of the conversation, not just the
     bubble's — a picked message is a picked ROW, which is what makes a
     column of them readable at a glance. */
  .msg.picked::before {
    content: ""; position: absolute;
    top: -3px; bottom: -3px; left: -1000px; right: -1000px;
    background: var(--selected);
  }
  .msg.picked > * { position: relative; }
  /* A flag anyone in the workspace can see, which is the only kind worth
     having in a shared inbox. */
  .starred {
    display: flex; align-items: center; gap: 4px;
    font-size: 11px; color: var(--star); margin: 3px 2px 0;
  }
  .starred svg { width: 13px; height: 13px; fill: var(--star); }
  body.selecting .messages { cursor: pointer; }
  /* Only where the pointer is a finger. A mouse keeps text selection —
     dragging across a message to copy part of it is a real thing to want,
     and a finger cannot do it anyway without fighting the hold. */
  @media (pointer: coarse) {
    .msg { -webkit-user-select: none; user-select: none;
           -webkit-touch-callout: none; }
  }
  body.selecting .bubble, body.selecting .vn * { pointer-events: none; }
  .btn.danger { background: var(--danger); color: #fff; }

  /* ---------- the overflow menu ---------- */
  .menu {
    position: absolute; top: 56px; right: 8px; z-index: 6;
    min-width: 210px; padding: 6px 0;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 12px; box-shadow: var(--shadow);
  }
  .menu .item {
    display: block; width: 100%; text-align: left;
    padding: 11px 18px; border: 0; background: transparent;
    color: var(--text); font: inherit; font-size: 14px; cursor: pointer;
  }
  .menu .item:hover { background: var(--hover); }
  .menu .item:disabled { opacity: .4; cursor: default; background: transparent; }
  /* ---- one menu per row: ⋮ on hover, right-click, press and hold ---- */
  .rowdots {
    display: grid; place-items: center; flex: none; align-self: center;
    width: 28px; height: 28px; margin-left: 4px; border-radius: 8px;
    color: var(--text-2); font-size: 18px; line-height: 1; cursor: pointer;
    opacity: 0; transition: opacity .12s;
  }
  .rowdots:hover { background: var(--hover); color: var(--text); }
  :is(.row, .callrow, .vmrow, .contactrow):hover .rowdots,
  :is(.row, .callrow, .vmrow, .contactrow):focus-within .rowdots,
  .row[aria-selected="true"] .rowdots, .rowdots:focus-visible { opacity: 1; }
  @media (hover: none) { .rowdots { display: none; } }
  :is(.row, .callrow, .vmrow, .contactrow) { -webkit-touch-callout: none; }
  :is(.row, .callrow, .vmrow, .contactrow).held { background: var(--hover); box-shadow: inset 0 0 0 2px var(--primary); }
  .menu.holdmenu { position: fixed; top: 0; right: auto; z-index: 40; min-width: 220px; max-height: calc(100vh - 16px); overflow-y: auto; }
  .holdmenu hr, .holdsheet hr { border: 0; border-top: 1px solid var(--border); margin: 4px 0; }
  .holdmenu .item.danger, .holdsheet .item.danger { color: var(--danger); }
  .holdsheet-wrap { position: fixed; inset: 0; z-index: 40; }
  .holdscrim { position: absolute; inset: 0; background: rgba(0,0,0,.45); }
  .holdsheet {
    position: absolute; left: 0; right: 0; bottom: 0; max-height: 80vh; overflow-y: auto;
    background: var(--surface); border-radius: 18px 18px 0 0; border-top: 1px solid var(--border);
    padding: 8px 12px calc(14px + env(safe-area-inset-bottom));
  }
  .holdsheet .grab { width: 36px; height: 4px; border-radius: 4px; background: var(--border); margin: 0 auto 12px; }
  .hold-head { display: flex; align-items: center; gap: 12px; padding: 0 6px 12px; }
  .hold-head .avatar { width: 40px; height: 40px; }
  .hold-who { display: flex; flex-direction: column; min-width: 0; }
  .hold-who span { color: var(--text-2); font-size: 13px; }
  .hold-quick { display: grid; grid-template-columns: repeat(auto-fit, minmax(70px, 1fr)); gap: 8px; margin-bottom: 8px; }
  .hold-quick button {
    border: 0; border-radius: 12px; padding: 12px 4px; background: var(--hover);
    color: var(--text); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
  }
  .holdsheet .item {
    display: block; width: 100%; text-align: left; padding: 13px 10px; border: 0; border-radius: 10px;
    background: transparent; color: var(--text); font: inherit; font-size: 15px; cursor: pointer;
  }
  .holdsheet .item:hover { background: var(--hover); }
  /* Selecting several conversations. */
  .bulkbar { display: flex; align-items: center; gap: 6px; padding: 6px 10px; border-bottom: 1px solid var(--border); }
  .bulkbar #bulk-count { font-weight: 600; margin-right: auto; }
  .bulk-acts { display: flex; flex-wrap: wrap; gap: 2px; }
  .row.picked { background: var(--hover); }
  .row.picked .avatar { background: var(--primary) !important; color: var(--on-primary) !important; }
  .row.picked .avatar > * { visibility: hidden; }
  .row.picked .avatar::after { content: "✓"; position: absolute; font-weight: 700; }
  .row .avatar { position: relative; display: grid; place-items: center; }
  /* The compose button's choices, opening up from it. */
  .menu.compose-menu { top: auto; right: 20px; bottom: 88px; min-width: 260px; }
  .compose-menu .item { display: flex; align-items: center; gap: 14px; padding: 12px 18px; }
  .compose-menu .item svg { width: 22px; height: 22px; flex: none; fill: var(--primary); }
  .compose-menu .item span { display: flex; flex-direction: column; }
  .compose-menu .item small { color: var(--text-2); font-size: 12px; margin-top: 2px; }
  .compose-menu .item.hidden { display: none; }

  /* A note is not a message and must not read like one: not a bubble, not
     on either side, and a colour no message ever has. */
  .note {
    align-self: center; max-width: min(78%, 560px);
    margin: 12px 0; padding: 12px 16px;
    background: var(--note-bg); border: 1px solid var(--note-chip);
    border-radius: 14px; font-size: 13px; line-height: 1.45;
    white-space: pre-wrap; overflow-wrap: anywhere;
  }
  .note .byline {
    display: flex; align-items: center; gap: 6px;
    color: var(--note-text); font-size: 11px; font-weight: 500;
    margin-bottom: 4px; text-transform: uppercase; letter-spacing: .6px;
  }
  .note .byline svg { width: 14px; height: 14px; }
  .note .drop {
    float: right; border: 0; background: transparent; cursor: pointer;
    color: var(--text-2); font-size: 11px; padding: 0 0 0 8px;
  }
  .composer-row { display: flex; align-items: flex-end; gap: 8px; }
  /* Recording puts the bar in the composer's place — but the row it
     replaces must not be DISPLAYED away, because hiding an element blurs
     whatever inside it had focus, and blurring the text field drops the
     keyboard. Tapping the microphone would put the keyboard away, and
     the page would jump twice: once as it left, once as it came back.
     Out of flow, no height, invisible and untouchable — but still
     rendered, so the caret stays exactly where it was. */
  .composer-row.stowed {
    position: absolute; height: 0; overflow: hidden;
    opacity: 0; pointer-events: none;
  }
  .pending {
    display: flex; flex-wrap: wrap; gap: 8px;
    padding: 0 0 10px 44px;
  }
  .pending:empty { display: none; }
  .att {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 6px 6px 6px 12px; border-radius: 16px;
    background: var(--surface-2); font-size: 13px; max-width: 260px;
  }
  .att span {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .att button {
    border: 0; background: transparent; color: var(--text-2);
    cursor: pointer; display: grid; place-items: center; padding: 0;
  }
  .att svg { width: 18px; height: 18px; }
  .att.busy { opacity: .6; }
  /* ---------- recording ----------
     What the phone shows while you are talking into it: how long you have
     been going, what the microphone is actually hearing, and — once the
     recording is locked and your thumb is free — throw it away, hold on,
     or send it.

     The live waveform is not decoration. A timer counting up tells you a
     recording is RUNNING; only the waveform tells you it is picking YOU
     up, which is the thing worth knowing before you have said thirty
     seconds into a muted microphone. */
  .recbar {
    display: flex; flex-direction: column; gap: 10px;
    padding: 10px 18px 12px; margin-bottom: 10px;
    background: var(--surface-2); border-radius: 24px;
  }
  .recline { display: flex; align-items: center; gap: 12px; }
  .recline #rectime {
    font-size: 14px; font-variant-numeric: tabular-nums; min-width: 34px;
  }
  .livewave {
    flex: 1; min-width: 0; height: 28px;
    display: flex; align-items: center; gap: 2px;
  }
  .livewave .bar {
    flex: 1; min-width: 0; height: 8%;
    border-radius: 2px; background: var(--text-2); opacity: .85;
  }
  .rechint { color: var(--text-2); font-size: 13px; text-align: center; }
  .reccontrols {
    display: flex; align-items: center; justify-content: space-between;
    padding: 0 4px;
  }
  .recpause {
    width: 48px; height: 48px; padding: 0;
    border: 0; border-radius: 50%; background: transparent;
    color: var(--danger); display: grid; place-items: center; cursor: pointer;
  }
  .recpause svg { width: 30px; height: 30px; }
  .reddot {
    width: 10px; height: 10px; border-radius: 50%; flex: none;
    background: var(--danger); animation: pulse 1.2s infinite;
  }
  /* Paused is a still picture: the dot stops, and so does the waveform. */
  .recbar.paused .reddot { animation: none; opacity: .4; }
  .recbar.paused .livewave { opacity: .45; }
  @keyframes pulse { 50% { opacity: .25; } }
  @media (prefers-reduced-motion: reduce) { .reddot { animation: none; } }
  .composer .inputwrap {
    flex: 1; display: flex; align-items: flex-end; gap: 8px;
    background: var(--surface-2);
    border-radius: 24px;
    padding: 6px 8px 6px 18px;
  }
  /* What the next message costs, once that stops being obvious. */
  .cost {
    align-self: flex-end; flex: none; padding: 0 2px 10px;
    font-size: 11px; color: var(--text-2);
    font-variant-numeric: tabular-nums;
  }
  .cost.over { color: var(--note-text); font-weight: 500; }
  /* The conversation list's three lenses. */
  .tabs { display: flex; gap: 6px; margin-left: auto; }
  .tabs button {
    border: 1px solid var(--border); background: transparent;
    color: var(--text-2); font: inherit; font-size: 12.5px;
    padding: 4px 12px; border-radius: 999px; cursor: pointer;
  }
  .tabs button[aria-selected="true"] {
    background: var(--primary-soft);
    border-color: transparent; color: var(--primary); font-weight: 500;
  }
  .who .bell { width: 14px; height: 14px; vertical-align: -2px;
               margin-left: 6px; fill: var(--text-2); }
  .who .pinned { width: 13px; height: 13px; vertical-align: -2px;
                 margin-left: 6px; fill: var(--text-2);
                 transform: rotate(35deg); }
  /* A snooze that has rung: the thread is back, saying why it is back. */
  .nap-due { font-size: 11px; margin-left: 6px; }
  /* The banner over a snoozed conversation. */
  .napbar {
    display: flex; align-items: center; gap: 10px;
    padding: 8px 16px; font-size: 12.5px; color: var(--text-2);
    background: var(--primary-soft);
  }
  .napbar button { border: 0; background: none; color: var(--primary);
                   font: inherit; font-size: 12.5px; font-weight: 500;
                   cursor: pointer; padding: 0; white-space: nowrap; }
  /* Suggested replies: a quiet row of taps above the composer. */
  .smart { display: flex; gap: 8px; padding: 8px 12px 0;
           overflow-x: auto; scrollbar-width: none; }
  .smart::-webkit-scrollbar { display: none; }
  .smart .chip {
    border: 1px solid transparent;
    background: var(--primary-soft);
    color: var(--primary); font: inherit; font-size: 13px;
    padding: 7px 14px; border-radius: 999px; cursor: pointer;
    white-space: nowrap; flex: none; max-width: 340px;
    overflow: hidden; text-overflow: ellipsis;
    animation: chipin .18s ease-out;
  }
  .smart .chip:hover { border-color: var(--primary); }
  @keyframes chipin { from { opacity: 0; transform: translateY(4px); } }
  #wand.busy svg { animation: wandspin 1s linear infinite; }
  @keyframes wandspin { to { transform: rotate(360deg); } }
  /* A tapback folded onto the bubble it reacted to. */
  .tap {
    position: absolute; bottom: -10px; font-size: 12px; line-height: 1;
    padding: 3px 6px; border-radius: 999px;
    background: var(--surface-2);
    border: 1px solid var(--border); box-shadow: 0 1px 2px rgba(0,0,0,.08);
    z-index: 1; pointer-events: none;
  }
  .msg.out .tap { left: -6px; }
  .msg.in .tap { right: -6px; }
  .msg.hastap { margin-bottom: 12px; }
  /* A message that exists but has not left yet. */
  .msg.sched .bubble {
    background: transparent; border: 1.5px dashed var(--border);
    color: var(--text);
  }
  /* What the first link in a message points at, once the server has
     looked: a title, the site, a line of description — no image, because
     img-src is 'self' and a preview is not worth widening that. */
  .linkcard {
    display: block; max-width: 320px; margin: 4px 0 2px;
    padding: 10px 12px; border-radius: 14px;
    background: var(--surface-2); border: 1px solid var(--border);
    color: var(--text); text-decoration: none; font-size: 13px;
  }
  .linkcard:hover { background: var(--hover); }
  .linkcard b { display: block; font-weight: 600; line-height: 1.3;
                overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2;
                -webkit-box-orient: vertical; }
  .linkcard .lc-desc { display: block; color: var(--text-2); margin-top: 3px;
                       overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2;
                       -webkit-box-orient: vertical; }
  .linkcard .lc-host { display: block; margin-top: 5px; color: var(--text-2);
                       font: 500 10.5px var(--mono); letter-spacing: .06em;
                       text-transform: uppercase; }
  .schedmeta { font-size: 11px; color: var(--text-2);
               margin: 4px 2px 6px; display: flex; gap: 10px; align-items: center; }
  .schedmeta.bad { color: var(--danger); }
  .schedmeta button {
    border: 0; background: none; color: var(--primary); font: inherit;
    font-size: 11px; font-weight: 500; cursor: pointer; padding: 0;
  }
  .transcript {
    font-size: 13px; color: var(--text-2); max-width: 280px;
    margin: 4px 6px 2px; font-style: italic;
  }
  /* Seated in the meta row, START of the `when` cell — the timestamp
     keeps the end. A grid child with NO area auto-places into a phantom
     extra row, which is exactly what this button used to do: an
     invisible band of dead space under every untranscribed note (and
     invisible twice over on our own bubbles, where --primary text sat
     on the --primary bubble). It writes in the bubble's own ink now. */
  .vn-tx { grid-area: when; justify-self: start; align-self: center;
           border: 0; background: none; color: inherit; opacity: .8;
           font: inherit; font-size: 11px; font-weight: 600;
           cursor: pointer; padding: 2px 8px 2px 0; }
  .composer textarea {
    flex: 1; border: 0; background: transparent; outline: none;
    resize: none; padding: 8px 0; max-height: 120px; line-height: 1.4;
  }
  /* Held, not tapped: the browser must not select text, scroll the
     thread, or raise a long-press menu under a finger that is recording. */
  @media print {
    /* "Print conversation" means the CONVERSATION: no rails, no composer,
       no buttons — the messages, flowing, however many pages they take. */
    nav, section.list, .thread-head .icon-btn, .composer, .fab, .jump,
    .selbar, #wall, .vn-tx, .schedmeta button { display: none !important; }
    section.thread { display: flex !important; }
    .messages { overflow: visible !important; height: auto !important; }
    body { background: #fff; }
  }
  #mic { touch-action: none; -webkit-user-select: none; user-select: none;
         -webkit-touch-callout: none; }
  #mic.holding { color: var(--danger); transform: scale(1.25); }
  .recbar.cancelling { color: var(--danger); }
  .recbar.cancelling .rechint { color: var(--danger); }
  .recbar.cancelling .livewave .bar { background: var(--danger); }

  .send {
    width: 40px; height: 40px; flex: none;
    display: grid; place-items: center;
    border: 0; border-radius: 50%;
    background: transparent; color: var(--text-2);
    cursor: pointer;
  }
  .send.ready { background: var(--primary); color: var(--on-primary); }
  .send:disabled { cursor: default; }
  .wall {
    flex: none; margin: 0 20px 14px; padding: 14px 16px;
    border-radius: 14px; background: var(--note-bg);
    color: var(--note-text); font-size: 13px; line-height: 1.45;
  }
  .wall .btn.text { margin: 8px 0 0 -12px; color: var(--note-text); }

  /* ---------- settings ---------- */
  .settings { flex: 1; overflow-y: auto; }
  body.settings-open section.list { display: none; }
  .settings .inner { max-width: 720px; margin: 0 auto; padding: 32px 24px 64px; }
  .settings h2 { font-size: 22px; font-weight: 400; margin: 0 0 24px; }

  /* Android-Settings shape: a home of category rows that open detail
     pages, each with a back arrow and sub-section headers. */
  .cat-card, .cat-row {
    display: flex; align-items: center; gap: 16px; width: 100%;
    text-align: left; background: none; border: 0; cursor: pointer;
    color: var(--text); font: inherit; padding: 14px 8px;
    border-radius: 12px;
  }
  .cat-card { padding: 16px; background: var(--surface-2); margin-bottom: 8px; }
  .cat-card:hover, .cat-row:hover { background: var(--surface-2); }
  .cat-ic {
    flex: none; width: 40px; height: 40px; border-radius: 50%;
    display: grid; place-items: center;
    background: color-mix(in srgb, var(--primary) 16%, transparent);
  }
  .cat-ic svg { width: 22px; height: 22px; fill: var(--primary); }
  .cat-body { flex: 1; min-width: 0; }
  .cat-body b { display: block; font-weight: 500; font-size: 15px; }
  .cat-body > span {
    display: block; color: var(--text-2); font-size: 13px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .cat-chev { width: 22px; height: 22px; fill: var(--text-2); flex: none; }
  .cat-head { display: flex; align-items: center; gap: 6px; margin: 0 0 8px; }
  .cat-head h2 { margin: 0; }
  .cat-back {
    background: none; border: 0; cursor: pointer; padding: 6px;
    display: grid; place-items: center; border-radius: 50%; color: var(--text);
  }
  .cat-back:hover { background: var(--surface-2); }
  .cat-back svg { width: 24px; height: 24px; fill: currentColor; }
  .subhead {
    font-size: 12px; font-weight: 600; text-transform: uppercase;
    letter-spacing: .8px; color: var(--primary); margin: 24px 0 0;
  }
  .subhead:first-child { margin-top: 4px; }

  .group { margin-bottom: 40px; }
  .group > h3 {
    font-size: 13px; font-weight: 500; text-transform: uppercase;
    letter-spacing: .8px; color: var(--text-2);
    margin: 0 0 4px; padding-bottom: 8px;
    border-bottom: 1px solid var(--border);
  }
  .setting {
    display: flex; align-items: center; gap: 24px;
    padding: 18px 0;
    border-bottom: 1px solid var(--border);
  }
  .setting .label { flex: 1; min-width: 0; }
  .setting .label b { display: block; font-weight: 500; }
  .setting .label span { color: var(--text-2); font-size: 13px; }
  .setting .control { flex: none; }
  select.control, .setting select {
    height: 36px; padding: 0 10px;
    border: 1px solid var(--border); border-radius: 4px;
    background: var(--surface);
  }
  .switch {
    width: 36px; height: 14px; border-radius: 7px;
    background: var(--border); position: relative; cursor: pointer;
    border: 0; padding: 0;
  }
  .switch::after {
    content: ""; position: absolute; top: -3px; left: 0;
    width: 20px; height: 20px; border-radius: 50%;
    background: var(--surface); box-shadow: var(--shadow);
    transition: left .15s;
  }
  .switch[aria-checked="true"] { background: color-mix(in srgb, var(--primary) 50%, transparent); }
  .switch[aria-checked="true"]::after { left: 16px; background: var(--primary); }
  .pwform { padding: 8px 0 20px; display: none; }
  .pwform.open { display: block; }
  .pwform .field { max-width: 380px; }
  .chips { display: flex; flex-wrap: wrap; gap: 8px; }
  .chip {
    padding: 6px 12px; border-radius: 16px;
    border: 1px solid var(--border); font-size: 13px; color: var(--text-2);
  }

  /* ---------- dialog ---------- */
  dialog {
    border: 0; border-radius: 8px; padding: 0;
    background: var(--surface); color: var(--text);
    box-shadow: var(--shadow); max-width: 440px; width: calc(100% - 48px);
  }
  dialog::backdrop { background: rgba(0,0,0,.5); }
  dialog .dlg { padding: 24px; }
  dialog h3 { margin: 0 0 20px; font-size: 18px; font-weight: 500; }
  dialog .actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }
  dialog textarea {
    width: 100%; min-height: 96px; resize: vertical;
    padding: 12px 14px; border: 1px solid var(--border);
    border-radius: 4px; background: transparent; outline: none;
  }
  #lightbox { max-width: 92vw; background: transparent; box-shadow: none; }
  #lightbox img { max-width: 92vw; max-height: 88vh; display: block; border-radius: 8px; }

  .toast {
    position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
    background: var(--toast-bg); color: var(--toast-text); padding: 14px 20px;
    border-radius: 4px; max-width: calc(100% - 32px); text-align: center;
    font-size: 13px; box-shadow: var(--shadow); z-index: 10;
  }

  /* ---------- phone ---------- */
  @media (max-width: 839px) {
    nav.rail { display: none; }
    section.list { width: 100%; border-right: 0; }
    section.newchat { width: 100%; border-right: 0; }
    section.thread { display: none; }
    .search { max-width: none; }
    .bar .spacer { display: none; }
    body.viewing section.list { display: none; }
    body.viewing section.thread { display: flex; }
    body.viewing header.bar { display: none; }
    /* Picking who to text takes the whole phone, exactly as it does on
       the phone: no app bar above it, no list behind it. */
    body.composing header.bar { display: none; }
    .thread-head .back { display: grid; }
    .msg { max-width: 82%; }
    .messages { padding: 16px 12px 4px; }
    .composer { padding: 8px 12px 14px; }
  }

/* ---------- the read-only support view ---------- */
/* An operator looking over the workspace's shoulder: one unmissable
   banner, and the acting tools put away. The SERVER refuses every
   mutation regardless — this is honesty, not enforcement. */
#support-banner {
  background: var(--warn-soft, #F6EFDF); color: var(--warn, #A56A00);
  text-align: center; font-size: 13px; font-weight: 600;
  padding: 8px 14px; position: sticky; top: 0; z-index: 60;
}
body.readonly #composer,
body.readonly #compose { display: none !important; }

/* ---- softphone: in-call overlay + dialer ---- */
/* The in-call view owns the screen and is dark in either theme, the same
 * choice the native app makes: a call is a mode, not a page. The layout
 * is the native InCallScreen verbatim: a held call's card at the top,
 * a knocking caller's card under it, the person centered over a soft
 * glow, the frosted control grid, and the hang-up pill. */
/* A call is a mode, not a page: the call screen and the dialer keep the
   night palette whichever theme the page wears, so the tokens their
   parts read are pinned to the dark values here. */
dialog.incall {
  color-scheme: dark;
  --surface: #0b0d10; --surface-2: #14171c;
  --hover: rgba(255,255,255,.07); --glass: rgba(255,255,255,.05);
  --glass-2: rgba(255,255,255,.08);
  --text: #eef1f5; --text-2: rgba(235,240,248,.55);
  --border: rgba(255,255,255,.08);
  --primary: #3fd39a; --on-primary: #06231a;
  --primary-soft: rgba(63,211,154,.14);
  --selected: rgba(63,211,154,.16); --selected-text: #bff0dd;
  --danger: #e5484d; --note-bg: #3a3323; --note-text: #fdd663;
}
dialog.incall {
  max-width: none; max-height: none;
  width: 100%; height: 100dvh; margin: 0; border-radius: 0;
  background: linear-gradient(180deg, #0d1017, #0b0d13 55%, #090b10);
  color: #f2f4f8;
  flex-direction: column; padding: 0; border: 0;
  position: relative; overflow: hidden;
}
/* Same [open] discipline as the dialer: a closed call screen must not
   keep covering the page. */
dialog.incall[open] { display: flex; }
dialog.incall::backdrop { background: #090b10; }
@media (min-width: 840px) {
  dialog.incall {
    width: 400px; height: min(760px, calc(100dvh - 48px));
    margin: auto; border-radius: 28px;
  }
  dialog.incall::backdrop { background: rgba(6, 8, 12, .86); }
}
/* The soft bloom behind the caller. */
dialog.incall::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 430px;
  background: radial-gradient(ellipse at 50% 30%,
    rgba(63, 211, 154, .16), transparent 70%);
  pointer-events: none;
}
.incall-scene {
  position: relative; display: flex; flex-direction: column;
  align-items: center; flex: 1; min-height: 0; width: 100%;
  padding: 14px 24px 0;
}
.held-card, .wait-card {
  display: flex; align-items: center; gap: 12px; width: 100%;
  border-radius: 22px; padding: 12px 14px; margin-bottom: 10px;
  background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.078);
}
.wait-card {
  background: rgba(52,158,82,.08); border-color: rgba(61,193,99,.24);
}
.held-card .avatar { width: 40px; height: 40px; font-size: 16px; flex: none; }
.held-meta {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px;
}
.held-name {
  font-size: 15px; font-weight: 700; color: #f2f4f8;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.held-sub { font-size: 12px; color: rgba(235,240,248,.38); }
.swap-pill {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  border-radius: 999px; padding: 9px 14px; cursor: pointer;
  background: var(--selected); border: 1px solid rgba(63,211,154,.3);
  color: var(--primary); font-size: 13px; font-weight: 700;
}
.swap-pill svg { width: 16px; height: 16px; fill: currentColor; }
.incall-id {
  flex: 1; min-height: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center;
}
.incall-avatar {
  width: 116px; height: 116px; border-radius: 50%;
  background: rgba(255,255,255,.08); color: #bff0dd;
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 22px; position: relative; font-weight: 600;
}
.incall-avatar svg { width: 64px; height: 64px; fill: currentColor; }
/* The design's green pulse ring around the caller. */
.incall-avatar::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  border: 1.5px solid rgba(63,211,154,.7); pointer-events: none;
  animation: incall-pulse 2.4s ease-out infinite;
}
@keyframes incall-pulse {
  0% { transform: scale(1); opacity: .6; }
  70% { transform: scale(1.55); opacity: 0; }
  100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .incall-avatar::after { animation: none; opacity: .45; }
}
/* Call-quality chip under the timer. */
.incall-quality {
  display: inline-flex; align-items: center; gap: 8px; margin-top: 14px;
  padding: 7px 14px; border-radius: 999px; background: rgba(255,255,255,.07);
  font: 500 10.5px var(--mono); letter-spacing: .08em; text-transform: uppercase;
  color: rgba(235,240,248,.72);
}
.incall-quality .q-dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--primary);
}
.incall-chips { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
                justify-content: center; }
/* The socket under the call is being brought back. */
.incall-quality.reconnecting .q-dot { background: var(--note-text);
                                      animation: pulse 1.2s infinite; }
/* The recording indicator: a small red-dot chip, never hidden by a
   setting — being recorded is not a preference. */
.incall-rec {
  display: inline-flex; align-items: center; gap: 7px; margin-top: 14px;
  padding: 7px 12px; border-radius: 999px;
  background: rgba(229,72,77,.16); border: 1px solid rgba(229,72,77,.35);
  font: 700 10.5px var(--mono); letter-spacing: .12em; color: #ffb4b6;
}
.incall-rec .rec-dot { width: 7px; height: 7px; border-radius: 50%;
                       background: #ff5c61; animation: pulse 1.6s infinite; }
/* ---- video ----------------------------------------------------------
   The far side's camera fills the scene and everything else sits on top of
   it. It is the LAYER that hides, not the identity block underneath: on an
   audio call — which is every call until the edge and the PBX both allow a
   video codec — the scene is exactly what it was, untouched. */
.vid-layer { position: absolute; inset: 0; z-index: 0; overflow: hidden;
             border-radius: inherit; background: #0b0d10; }
.vid-remote { position: absolute; inset: 0; width: 100%; height: 100%;
              object-fit: cover; background: #0b0d10; }
/* The self-view, mirrored the way every camera app mirrors it — a person
   moving their right hand expects the image to move right.
   In the FLOW, not in the absolute layer: end-aligned in the scene's
   column, directly above .incall-foot, so it cannot land on the controls.
   flex: none for the reason .listcard needs it — a flex item whose
   overflow is not visible has an automatic minimum size of zero, and this
   one would otherwise be crushed to nothing on a short screen. */
.vid-local {
  align-self: flex-end; flex: none; width: 104px; height: 148px;
  margin: 0 0 14px; object-fit: cover; border-radius: 18px;
  background: #14171c; border: 1px solid rgba(255,255,255,.16);
  transform: scaleX(-1); box-shadow: 0 8px 24px rgba(0,0,0,.45);
}
/* Camera off: the track is still there sending black frames, so the
   preview goes rather than leaving a black hole that reads as broken.
   display:none, not visibility:hidden — in the flow it would otherwise
   hold its 148px and push the controls down. */
.vid-local.off { display: none; }
/* With a face on screen the name still has to be readable, so the identity
   block keeps its place and gains a scrim of its own rather than moving. */
.incall-scene.video .incall-id { justify-content: flex-start; padding-top: 8px; }
.incall-scene.video .incall-avatar { display: none; }
.incall-scene.video .incall-name,
.incall-scene.video .incall-number,
.incall-scene.video .incall-status {
  text-shadow: 0 1px 10px rgba(0,0,0,.85);
}
.incall-scene.video .incall-name { font-size: 20px; }
/* Scrims, not just a text-shadow: a name, a timer and a row of glass
   tiles have to stay readable over a picture nobody chose — a bright
   window, a white wall, a lamp. Drawn inside the video layer so they
   sit over the far side and under everything else. */
.vid-layer::before, .vid-layer::after {
  content: ""; position: absolute; left: 0; right: 0; pointer-events: none;
}
.vid-layer::before {
  top: 0; height: 190px;
  background: linear-gradient(180deg, rgba(5,7,10,.80), rgba(5,7,10,0));
}
.vid-layer::after {
  bottom: 0; height: 280px;
  background: linear-gradient(0deg, rgba(5,7,10,.93), rgba(5,7,10,0));
}
.incall-scene.video .incall-quality { display: none; }
/* Everything that is not the video sits above it. */
.incall-scene > *:not(.vid-layer) { position: relative; z-index: 1; }
/* Asked for video, did not get it. */
.incall-vidnote {
  display: inline-flex; align-items: center; margin-top: 14px;
  padding: 7px 12px; border-radius: 999px;
  background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.1);
  font: 500 10.5px var(--mono); letter-spacing: .06em; text-transform: uppercase;
  color: rgba(235,240,248,.72);
}
.incall-name { font-size: 28px; font-weight: 700; text-align: center; }
.incall-number {
  font-size: 16px; color: rgba(235,240,248,.62); margin-top: 8px;
}
.incall-status {
  font-size: 15px; color: rgba(235,240,248,.38); margin-top: 8px;
  min-height: 20px; text-align: center;
}
.incall-status.timer { color: var(--primary); font-weight: 700; }
.incall-status.failed { color: var(--danger); }
/* Conference mode: the identity shrinks and the room's people take the
   middle of the screen. */
.incall-scene.conf .incall-id { flex: none; padding: 8px 0 12px; }
.incall-scene.conf .incall-avatar { display: none; }
.incall-scene.conf .incall-name { font-size: 24px; }
.conf-list {
  flex: 1; min-height: 0; width: 100%; overflow-y: auto;
  display: flex; flex-direction: column; gap: 8px; padding-bottom: 10px;
}
.conf-row {
  display: flex; align-items: center; gap: 12px;
  border-radius: 20px; padding: 10px 12px;
  background: rgba(255,255,255,.043);
  border: 1px solid rgba(255,255,255,.059);
}
.conf-row .avatar { width: 38px; height: 38px; font-size: 15px; flex: none; }
.conf-row .round.small { background: rgba(255,255,255,.08); }
.conf-row .round.small.on,
.conf-row .round.small.kick { background: rgba(242,139,130,.2); }
.incall-foot {
  width: 100%; display: flex; flex-direction: column; align-items: center;
  padding: 0 24px max(30px, env(safe-area-inset-bottom));
}
.ring-block, .ctrl-block {
  width: 100%; display: flex; flex-direction: column; align-items: center;
}
.scene-pill {
  display: inline-flex; align-items: center; gap: 8px;
  border-radius: 999px; padding: 11px 18px; margin-bottom: 34px;
  background: rgba(255,255,255,.055);
  border: 1px solid rgba(255,255,255,.078);
  color: #f2f4f8; font-size: 13px; cursor: pointer;
}
.scene-pill svg { width: 17px; height: 17px; fill: currentColor; }
.ring-row {
  width: 100%; display: flex; justify-content: space-between;
  padding: 0 18px; margin-bottom: 10px;
}
.biground {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.biground > span { font-size: 13px; color: rgba(235,240,248,.62); }
.round {
  width: 64px; height: 64px; border-radius: 50%; border: 0;
  background: #3c4043; color: #fff; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  flex: none;
}
.round svg { width: 28px; height: 28px; fill: currentColor; }
.round.answer { background: linear-gradient(135deg, #3dc163, #1e9e4a); }
.round.hangup { background: linear-gradient(135deg, #f0574c, #d93025); }
.round.active { background: #e8eaed; color: #202124; }
.round.big { width: 78px; height: 78px; }
.round.big svg { width: 34px; height: 34px; }
.round.small { width: 40px; height: 40px; }
.round.small svg { width: 19px; height: 19px; }
.round:hover { filter: brightness(1.12); }

/* ---- the ring banner ---- */
/* An incoming call knocks at the top of the page in a pill, over whatever
   the person is doing, instead of taking the screen: the call palette,
   the caller, the line it rang on, and Answer and Decline where the hand
   expects them. Fixed to the viewport and above everything the page
   stacks (the support banner's 60 is the next layer down); a modal
   dialog's top layer would still cover it, which is why renderIncall
   opens the call screen instead while one is up. */
.ring-banner {
  color-scheme: dark;
  --ring-x: -50%;
  position: fixed; left: 50%; top: max(14px, env(safe-area-inset-top));
  transform: translateX(var(--ring-x)); z-index: 70;
  width: 580px; max-width: calc(100vw - 24px); height: 76px;
  padding: 0 12px; box-sizing: border-box; border-radius: 999px;
  display: flex; align-items: center; gap: 14px;
  background: #14171c; color: #f2f4f8; font-family: var(--ui);
  border: 1px solid rgba(255,255,255,.13);
  box-shadow: 0 18px 48px rgba(0,0,0,.6), 0 2px 8px rgba(0,0,0,.4);
  overflow: hidden;
  animation: ring-banner-in .28s cubic-bezier(.2,.8,.2,1);
}
/* The green bloom behind the caller, as on the call screen. */
.ring-banner::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(ellipse at 10% 50%, rgba(63,211,154,.26), transparent 55%);
}
@keyframes ring-banner-in {
  from { transform: translate(var(--ring-x), -24px); opacity: 0; }
  to { transform: translate(var(--ring-x), 0); opacity: 1; }
}
/* The caller is the door to the full call screen. */
.ring-who {
  position: relative; flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 14px;
  padding: 6px 12px 6px 6px; margin: 0; border: 0; border-radius: 999px;
  background: transparent; color: inherit; font: inherit; text-align: left;
  cursor: pointer;
}
.ring-who:hover { background: rgba(255,255,255,.05); }
.ring-banner .avatar.ring-avatar {
  position: relative; width: 52px; height: 52px; font-size: 18px; font-weight: 600;
}
.ring-banner .avatar.ring-avatar svg { width: 26px; height: 26px; }
/* The pulse ring around the caller: the banner is ringing, not resting. */
.ring-avatar::after {
  content: ""; position: absolute; inset: -5px; border-radius: 50%;
  border: 2px solid rgba(63,211,154,.55); pointer-events: none;
  animation: ring-banner-pulse 2.4s ease-out infinite;
}
@keyframes ring-banner-pulse {
  0% { transform: scale(1); opacity: .85; }
  70% { transform: scale(1.22); opacity: 0; }
  100% { opacity: 0; }
}
.ring-text {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px;
}
.ring-text > * {
  display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ring-label {
  font: 500 10.5px var(--mono); letter-spacing: .08em; text-transform: uppercase;
  color: #3fd39a;
}
.ring-name { font-size: 16px; font-weight: 700; line-height: 1.25; }
.ring-number { font-size: 12.5px; color: rgba(235,240,248,.62); }
/* Reply with a text: a ghost beside the two real buttons. */
.ring-ghost {
  position: relative; width: 44px; height: 44px; border-radius: 50%; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.13);
  color: rgba(235,240,248,.85); cursor: pointer;
}
.ring-ghost:hover { background: rgba(255,255,255,.1); }
.ring-ghost svg { width: 20px; height: 20px; fill: currentColor; }
.ring-banner .round { position: relative; width: 52px; height: 52px; }
.ring-banner .round svg { width: 24px; height: 24px; }
.ring-banner :focus-visible { outline: 2px solid #3fd39a; outline-offset: 2px; }
@media (max-width: 839px) {
  /* A phone's width: edge to edge, rounder, no room for the ghost. */
  .ring-banner {
    --ring-x: 0;
    left: 12px; right: 12px; top: max(12px, env(safe-area-inset-top));
    width: auto; max-width: none; height: 74px; border-radius: 24px;
    gap: 8px; padding: 0 10px;
  }
  .ring-who { gap: 10px; padding: 4px 6px 4px 4px; }
  .ring-banner .avatar.ring-avatar { width: 46px; height: 46px; font-size: 16px; }
  .ring-ghost { display: none; }
  .ring-banner .round { width: 48px; height: 48px; }
}
@media (prefers-reduced-motion: reduce) {
  .ring-banner { animation: none; }
  .ring-avatar::after { animation: none; opacity: .6; }
}
/* The frosted control tiles, three to a row like the native grid. */
.tile-row { width: 100%; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 12px; }
/* A video call adds the Camera tile to the first row: four across, rather
   than a third row with one tile sitting on its own. */
.tile-row.four { grid-template-columns: repeat(4, 1fr); }
/* The six tiles are two pages of three-by-two on one sliding track: More
   slides the second page in from the right, Back slides it back out. The
   page off screen is also `inert` (app.js), so the keyboard never lands
   on a tile nobody can see. */
.ctrl-pages { width: 100%; overflow: hidden; }
.ctrl-track { display: flex; width: 200%; transition: transform .24s ease; }
.ctrl-page { width: 50%; flex: none; }
.ctrl-block.page2 .ctrl-track { transform: translateX(-50%); }
@media (prefers-reduced-motion: reduce) { .ctrl-track { transition: none; } }
.glass-tile.pager .tile-box svg { width: 30px; height: 30px; }
/* A seat nothing fills on the second page: the tiles keep their size. */
.tile-seat { min-width: 0; }
/* Folded (a short window, a video call): the second row and page have
   moved into the More sheet, so neither takes any room here. */
.ctrl-block.folded #ctrl-row-2, .ctrl-block.folded #ctrl-page-2 { display: none; }
#more-call-sheet .tile-row { margin-bottom: 0; }
.glass-tile {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
  align-items: center; gap: 8px; border: 0; background: none;
  color: inherit; cursor: pointer; padding: 0; font: inherit;
}
.tile-box {
  width: 100%; height: 68px; border-radius: 24px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.067);
  border: 1px solid rgba(255,255,255,.078);
}
.tile-box svg { width: 26px; height: 26px; fill: #f2f4f8; }
.glass-tile:hover .tile-box { background: rgba(255,255,255,.1); }
/* A control the browser can't honor (Speaker where output can't be routed)
   reads as unavailable — dimmed, but still tappable so a tap can say why. */
.glass-tile.unavailable { opacity: .4; }
.glass-tile.unavailable:hover .tile-box { background: rgba(255,255,255,.067); }
.tile-label { font-size: 12px; color: rgba(235,240,248,.62); }
.glass-tile.on .tile-box { background: #e8eaed; border-color: #e8eaed; }
.glass-tile.on .tile-box svg { fill: #17191e; }
.glass-tile.accent .tile-box {
  background: var(--selected); border-color: rgba(63,211,154,.34);
}
.glass-tile.accent .tile-box svg { fill: var(--primary); }
.glass-tile.accent .tile-label { color: var(--primary); font-weight: 700; }
.end-pill {
  width: 78px; height: 78px; border-radius: 50%; border: 0;
  background: linear-gradient(135deg, #f0574c, #d93025); color: #fff;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; margin-top: 10px; flex: none;
}
.end-pill svg { width: 30px; height: 30px; fill: currentColor; }
.end-pill:hover { filter: brightness(1.12); }
.wide-pill {
  width: 200px; height: 60px; border-radius: 30px; border: 0;
  background: linear-gradient(135deg, #2a2f3a, #232833); color: #f2f4f8;
  font-size: 16px; font-weight: 700; cursor: pointer; margin-top: 10px;
}
.wide-pill.go {
  width: 100%; max-width: 280px; height: 54px; border-radius: 27px;
  background: linear-gradient(135deg, #3dc163, #1e9e4a); color: #fff;
  margin-top: 14px;
}
/* ---- the sheets over a call: keypad (DTMF), a number to add or
   transfer to, the decline texts. Native's SheetScrim, bottom-anchored. */
.sheet-scrim {
  position: absolute; inset: 0; background: rgba(6,8,12,.6); z-index: 5;
}
.call-sheet {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 6;
  display: flex; flex-direction: column; align-items: center;
  background: #14181f; border-radius: 28px 28px 0 0;
  padding: 10px 26px calc(24px + env(safe-area-inset-bottom));
}
.sheet-grip {
  width: 42px; height: 4px; border-radius: 2px;
  background: rgba(255,255,255,.22); margin-bottom: 14px; flex: none;
}
.sheet-title { font-size: 16px; font-weight: 700; margin-bottom: 12px; }
.sheet-sub {
  font-size: 12px; color: rgba(235,240,248,.38); margin-bottom: 14px;
}
.dtmf-typed {
  font-size: 30px; font-weight: 700; letter-spacing: 5px; min-height: 38px;
  max-width: 100%; overflow: hidden; white-space: nowrap;
}
.sheet-keys {
  padding: 0; grid-template-columns: repeat(3, 64px); gap: 10px 22px;
}
.sheet-keys .dial-key {
  width: 64px; height: 64px; font-size: 22px; border-radius: 22px;
  border: 1px solid rgba(255,255,255,.078);
}
.sheet-link {
  border: 0; background: none; color: var(--primary); font-size: 14px;
  font-weight: 700; cursor: pointer; margin-top: 14px; padding: 6px 10px;
}
/* "Transfer to a contact": the book, searched from inside the sheet. */
.num-search { width: 100%; max-width: 300px; position: relative; margin-bottom: 6px; }
.num-search input {
  width: 100%; height: 38px; padding: 0 14px; border-radius: 999px;
  border: 1px solid rgba(255,255,255,.1); background: rgba(255,255,255,.06);
  color: #f2f4f8; outline: none; font-size: 14px;
}
.num-search input::placeholder { color: rgba(235,240,248,.4); }
.num-matches { display: flex; flex-direction: column; gap: 4px; margin-top: 6px;
               max-height: 150px; overflow-y: auto; }
.num-matches:empty { display: none; }
.num-matches button {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; text-align: left; border: 0; border-radius: 12px;
  padding: 9px 12px; cursor: pointer; font: inherit;
  background: rgba(255,255,255,.06); color: #f2f4f8; font-size: 13.5px;
}
.num-matches button span { color: rgba(235,240,248,.55); font: 400 11.5px var(--mono); }
.num-matches button:hover { background: rgba(255,255,255,.1); }
.num-row {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 10px; min-height: 40px;
}
.num-display { font-size: 24px; font-weight: 700; }
.num-display.empty { color: rgba(235,240,248,.38); }
.num-row .icon-btn svg { fill: rgba(235,240,248,.62); }
.reply-list { width: 100%; display: flex; flex-direction: column; gap: 8px; }
.reply-row {
  text-align: left; border-radius: 18px; padding: 14px 16px; cursor: pointer;
  background: rgba(255,255,255,.067);
  border: 1px solid rgba(255,255,255,.078);
  color: #f2f4f8; font-size: 14px; font-family: inherit;
}
.reply-row:hover { background: rgba(255,255,255,.1); }

/* The dialer is the native DialpadScreen, verbatim: a full scene on a
   phone, a tall rounded card on a desktop — back arrow + "New call",
   two recents as avatar chips, the number huge and centered in the
   breathing room, circular keys with their letters, the green call
   button with backspace beside it. */
.dialpad {
  max-width: none; max-height: none;
  width: 100%; height: 100dvh; margin: 0; border-radius: 0;
  background: linear-gradient(180deg, #12161f, #0b0d13);
  color: var(--text);
  flex-direction: column;
}
/* Only while OPEN — an unconditional display would override the UA's
   dialog:not([open]) { display:none } and leave a closed dialer
   covering the whole page. */
.dialpad[open] { display: flex; }
.dialpad::backdrop { background: rgba(6, 8, 12, .86); }
@media (min-width: 840px) {
  .dialpad {
    width: 400px; height: min(760px, calc(100dvh - 48px));
    margin: auto; border-radius: 28px;
  }
}
/* The number sits low, just above the keys, left-aligned like the mock. */
.dial-display {
  flex: 1; min-height: 0; display: flex; flex-direction: column;
  justify-content: flex-end; padding: 0 26px 22px;
}
/* `Type.dial` is 44sp at FontWeight.Light with 0.5sp of tracking
   (Design.kt:107, CallScreen.kt:2527) — the number is drawn thin and
   open, not set solid at 500. */
.dial-num {
  width: 100%; border: 0; outline: 0; background: transparent;
  color: var(--text); font: 300 42px/1 var(--ui);
  text-align: left; padding: 0; letter-spacing: .01em;
}
.dial-sub {
  font: 400 12.5px var(--mono); letter-spacing: .04em;
  color: var(--text-2); margin-top: 9px; min-height: 15px;
  text-transform: uppercase;
}
/* A sentence, not a label: the emergency notice keeps its own case. */
.dial-sub.warn {
  font: 500 13px/1.35 var(--ui); letter-spacing: 0; text-transform: none;
  color: #fdd663;
}
.dial-keys {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 12px; padding: 0 22px;
}
.dial-key {
  height: 70px; border: 0; border-radius: 24px;
  background: rgba(255,255,255,.06); color: var(--text);
  font: 400 27px/1 var(--ui); cursor: pointer;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px;
}
.dial-key small {
  font: 500 8.5px/1 var(--mono); letter-spacing: .16em;
  color: rgba(235,240,248,.38); min-height: 9px;
}
.dial-key:hover { background: rgba(255,255,255,.1); }
.dial-key:active { background: rgba(255,255,255,.14); }
/* The quick-dial row: favorites as avatar chips above the number. */
.dial-rec {
  text-align: center; padding: 12px 24px 0;
  font: 500 10.5px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: #ffb4b6;
}
.dial-foot {
  display: flex; align-items: center; justify-content: center;
  padding: 22px 0 max(24px, env(safe-area-inset-bottom));
}
.dial-side { width: 74px; display: flex; justify-content: center; }
.dial-foot .round { width: 78px; height: 78px; margin: 0 26px; }
.dial-foot .round svg { width: 30px; height: 30px; }
/* The design's soft glow ring around the green call button. */
.dial-call { box-shadow: 0 0 0 10px rgba(63,211,154,.12); }
.dial-call:hover { box-shadow: 0 0 0 10px rgba(63,211,154,.18); }
/* Video: Call's quieter sibling, in the counterweight beside it. Lit and
   dim differ in HUE, not only in strength — a tinted disc that only
   loses opacity is a near-white disc either way on the light ground, and
   the two states have to be told apart at a glance at this size. */
.dial-video {
  width: 52px; height: 52px; border-radius: 50%;
  display: grid; place-items: center; border: 0; padding: 0; cursor: pointer;
  background: color-mix(in srgb, var(--primary) 22%, transparent);
  color: var(--primary);
}
.dial-video svg { width: 24px; height: 24px; fill: currentColor; }
.dial-video:hover { filter: brightness(1.12); }
.dial-video[aria-disabled="true"] {
  background: color-mix(in srgb, var(--text-2) 14%, transparent);
  color: var(--faint);
  filter: none;
}

  /* ---------- the phone tabs (Calls / Contacts / Voicemail), v2.8 ---- */
  .phonepane {
    flex: 1; min-width: 0; min-height: 0;
    position: relative;
    display: none; flex-direction: column;
    background: transparent;
  }
  body.tab-calls #callspane,
  body.tab-detail #calldetail,
  body.tab-contacts #contactspane,
  body.tab-voicemail #vmpane { display: flex; }
  body.tab-calls section.list, body.tab-calls section.thread,
  body.tab-detail section.list, body.tab-detail section.thread,
  body.tab-contacts section.list, body.tab-contacts section.thread,
  body.tab-voicemail section.list, body.tab-voicemail section.thread {
    display: none;
  }
  .pane-head {
    flex: none;
    display: flex; align-items: center; justify-content: flex-end;
    padding: 6px 18px 4px;
    font: 600 32px var(--ui); letter-spacing: -.03em; color: var(--text);
  }
  /* Contacts' head is the bare word with nothing beside it, so hiding
     the word leaves an empty bar; it goes entirely. */
  #contactspane > .pane-head { display: none; }
  /* Voicemail's head leads with its back button, so its name is the last
     child rather than the first; Settings draws "← Settings" where the
     app draws a back arrow alone (SettingsScreen.kt:186). Both names go
     the same way as the other four. */
  #vmpane > .pane-head > span:last-child,
  #settings-home .cat-head h2 {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  /* A quiet link in the phone header — e.g. "Voicemail" under the Phone tab. */
  .head-link {
    border: 0; background: none; cursor: pointer;
    font: 500 13px var(--ui); color: var(--primary);
    padding: 6px 4px;
  }
  .chiprow {
    flex: none;
    display: flex; gap: 8px;
    padding: 4px 16px 10px;
  }
  .chip {
    border: 0;
    background: var(--glass-2);
    color: var(--text-2);
    font: 500 12.5px var(--ui);
    padding: 7px 15px;
    border-radius: 999px;
    cursor: pointer;
  }
  .chip[aria-selected="true"] { background: var(--nav-on); color: var(--nav-on-text); font-weight: 600; }
  /* Recents and contacts: the same sheet, the same hairlines (CallRow and
     ContactLine in the app). A call's avatar is round there, a
     contact's a rounded square. */
  #call-rows, #contact-rows {
    padding: 0 0 14px;
    display: flex; flex-direction: column; gap: 0;
  }
  .contactrow {
    display: flex; gap: 13px; align-items: center;
    width: 100%; position: relative;
    padding: 12px 12px 12px 20px;
    border: 0; text-align: left; color: var(--text);
    cursor: pointer;
    background: transparent; border-radius: 0;
  }
  /* A call row OPENS where it sits, so it is a container now and the
     head is the part that was the row. Its actions hang underneath,
     lined up with the name rather than the avatar. */
  .callrow { position: relative; width: 100%; background: transparent; }
  /* The app's spacing, child by child, instead of one gap between all of
     them: the dot column sits straight against the avatar, ListRow.gap
     (12) follows the avatar, Space.s8 comes before the time, and the
     round button carries its own air (painted 38, touched 48 —
     Glass.kt:196-200). A blanket 13px gap here spent 52px on four gaps
     where the app spends 20, and that difference was the whole reason a
     same-day missed call still read "rang 2…". */
  .call-head {
    display: flex; gap: 0; align-items: center;
    width: 100%;
    padding: 12px 12px 12px 8px;
    border: 0; text-align: left; color: var(--text);
    cursor: pointer;
    background: transparent; border-radius: 0;
  }
  .call-head > .avatar { margin-right: 12px; }
  .call-head > time { margin-left: 8px; }
  .call-head:hover, .contactrow:hover { background: var(--hover); }
  .call-head[aria-expanded="true"] .who b { color: var(--primary); }
  /* What a row opens: a round button with its name under it, the way a
     phone's dialer opens a call. It used to be a strip of outlined
     words, which read as a form rather than as part of the row. */
  .call-actions {
    display: flex; gap: 22px;
    padding: 0 20px 14px 73px;
  }
  .rowact {
    display: flex; flex-direction: column; align-items: center; gap: 5px;
    border: 0; background: transparent; padding: 0; cursor: pointer;
    color: var(--text-2); font: 500 10.5px var(--ui); letter-spacing: .2px;
  }
  .rowact .tile {
    width: 38px; height: 38px; border-radius: 50%;
    background: var(--primary-soft); color: var(--primary);
    display: grid; place-items: center;
  }
  .rowact:hover .tile { background: var(--hover); }
  .rowact svg { width: 18px; height: 18px; }
  .contactrow.selected { background: var(--selected); }
  .callrow.selected .call-head { background: var(--selected); }
  /* 14px, like every other list and like Android's Radius.controlShape
     (CallsScreen.kt:500) — Recents was the one drawing a circle. */
  .callrow .avatar { border-radius: 14px; }
  .contactrow .avatar { border-radius: 14px; }
  .callrow .who, .contactrow .who { flex: 1; min-width: 0; }
  .callrow .who b, .contactrow .who b {
    display: block; font: 500 15px var(--ui); color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .callrow.missed .who b { color: var(--missed); font-weight: 600; }
  .callrow .what, .contactrow .what {
    display: flex; align-items: center; gap: 5px;
    font-size: 13px; color: var(--text-2); margin-top: 2px;
  }
  .contactrow .what { font: 400 13px var(--ui); }
  .contactrow .notes {
    font: 400 12px var(--ui); color: var(--text-2); margin-top: 2px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .callrow .what svg { width: 14px; height: 14px; }
  .callrow.missed .what svg { color: var(--missed); }
  .callrow time { font: 500 11px var(--mono); color: var(--text-2); flex: none; }

  /* ---------- every list is the same sheet ----------

     One rounded glass card per group — a day in Recents, Voicemail and
     Messages, a letter in Contacts — with the rows inside it and the
     hairline BETWEEN them: the same 20px group Settings has always
     used, so no list in the app is drawn as a bare rail any more. */
  .listcard {
    border-radius: 20px; background: var(--card);
    overflow: hidden; margin: 0 0 10px;
    /* flex: none, and it is not cosmetic. The list is a column flex
       container that scrolls, and a flex item whose own overflow is not
       `visible` has an automatic minimum size of ZERO — so this card,
       which needs overflow:hidden to keep its rows inside its corners,
       is free to be shrunk to whatever is left of the screen. Shrunk,
       the rows are clipped AND nothing overflows the list, so the page
       stops scrolling entirely. A card in a scrolling list keeps its own
       height and lets the list scroll past it. */
    flex: none;
  }
  /* The rail the bare lists drew under EVERY row is the card's job now.
     Left on, it doubles the hairline and runs one under the last row,
     along the card's own bottom edge. */
  .listcard > .row::after, .listcard > .callrow::after,
  .listcard > .contactrow::after, .listcard > .vmrow::after { display: none; }
  /* Drawn by each row AFTER the first, so there is never one above the
     first or below the last. The inset is the card's to say, since each
     list seats its avatar at its own depth. */
  .listcard > * + *::before {
    content: ""; position: absolute; top: 0; height: 1px;
    left: var(--hair-left, 73px); right: var(--hair-right, 16px);
    background: var(--border);
  }
  #call-rows .listcard { --hair-left: 72px; --hair-right: 10px; }
  .call-card .call-head { padding-left: 8px; padding-right: 10px; }
  .call-card .call-actions { padding-left: 72px; }
  /* The number reads as a number: mono, tighter, and a shade quieter
     than the words around it. */
  /* The number reads in the row's own face, at the row's own size: the
     app draws the whole second line as ONE Type.meta string, number and
     summary together (CallsScreen.kt:466-472, `under`). Mono at 12px
     drew it wider than the app draws it and in a face nothing else on
     the line uses, which is what pushed "Rang 22s" onto a second line
     once the missed-call dot took its 16px column. */
  .call-num { font: 400 13px var(--ui); flex: none; }
  .call-sep { opacity: .5; flex: none; }
  .call-chip {
    font: 500 9.5px var(--mono);
    text-transform: uppercase; letter-spacing: .9px;
    padding: 2px 6px; border-radius: 5px;
    background: rgba(255,255,255,.07); color: var(--faint, var(--text-2));
    flex: none;
  }
  .call-chip.read { background: var(--primary-soft); color: var(--primary); }
  .call-count {
    font: 500 11px var(--mono); color: var(--text-2);
    background: rgba(255,255,255,.07);
    border-radius: 999px; padding: 1px 7px;
  }
  /* The line with the most on it — the arrow, the number, how long, the
     line that took it, whether it has been read — WRAPS rather than
     truncating. It used to ellipsise, and on a named contact with a
     recording there was enough on it that "0:30" came out as "0..": a
     row that hides how long a call was in order to fit a chip has
     thrown away the more useful of the two. */
  .callrow .what { flex-wrap: wrap; row-gap: 3px; }
  /* ...but the WORDS stay on one line and take the ellipsis, which is
     what the app does with them: number and summary are one
     `maxLines = 1, overflow = Ellipsis` Text (CallsScreen.kt:512-514).
     Only the chips below wrap. Without this a missed call on a
     same-day row — the longest line Recents draws, and the one the
     missed-call dot's 16px column finally pushed over — broke in two. */
  .callrow .call-line {
    display: flex; align-items: center; gap: 0;
    min-width: 0; flex: 1 1 auto;
  }
  /* One gap, after the glyph — Space.s4, the only spacer `CallRow` puts
     on this line (CallsScreen.kt:508). The number, the separator and the
     summary are one run of text on the phone, so they get no gaps of
     their own here either; the separator carries its own spaces the way
     " · " does inside the app's string. */
  .callrow .call-line > svg { margin-right: 4px; }
  .callrow .call-sep { margin: 0 4px; }
  .callrow .call-brief {
    min-width: 0; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* The round ghost button of the app's rows (GhostRound): a breath of
     frost over the sheet, the accent for its glyph. */
  .ghostbtn {
    flex: none;
    width: 38px; height: 38px; border-radius: 50%;
    border: 0; padding: 0; cursor: pointer;
    background: var(--glass); color: var(--primary);
    display: grid; place-items: center;
  }
  .ghostbtn:hover { background: var(--hover); }
  .ghostbtn svg { width: 18px; height: 18px; }
  .ghostbtn.quiet { color: var(--text-2); }

  /* ---------- favorites ---------- */
  .favstrip {
    flex: none; display: flex; gap: 14px; padding: 4px 18px 12px;
    overflow-x: auto; scrollbar-width: none;
    width: 100%; max-width: 880px; margin: 0 auto;
  }
  .favstrip::-webkit-scrollbar { display: none; }
  .favstrip button {
    flex: none; display: flex; flex-direction: column; align-items: center; gap: 6px;
    width: 66px; border: 0; background: none; color: var(--text-2);
    font: 500 11.5px var(--ui); cursor: pointer; padding: 0;
  }
  .favstrip button .avatar { width: 50px; height: 50px; border-radius: 18px; }
  .favstrip button span:last-child {
    max-width: 66px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .favstrip button:hover { color: var(--text); }

  /* ---------- voicemail ---------- */
  /* Voicemail's list is `.rows`, exactly as Messages', Recents' and
     Contacts' are — it had an id-level copy of that class here, every
     declaration identical, which did nothing except OUTRANK
     `.phonepane > .rows`'s 104px of clearance for the floating nav. The
     list scrolled; its last message just sat under the pill for ever,
     which reads as a list that will not scroll to the end. No other list
     carries such a rule and this one does not need one. */
  .vmrow {
    display: flex; flex-direction: column; gap: 10px;
    padding: 13px 14px; border-radius: 0;
    background: transparent; color: var(--text);
  }
  /* Unheard reads in the type — the bold name, the dot — not in a tint. */
  .vmrow.unheard .who b { font-weight: 700; }
  .vm-head { display: flex; gap: 13px; align-items: center; }
  .vm-head .avatar { border-radius: 14px; }
  .vm-head .who { flex: 1; min-width: 0; }
  .vm-head .who b {
    display: block; font: 600 15px var(--ui);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .vm-head .who .what { font: 400 13px var(--ui); color: var(--text-2); margin-top: 2px; }
  .vm-head .dot { flex: none; }
  .vm-play {
    display: flex; align-items: center; gap: 10px;
  }
  .vm-play .listen { flex: 1; min-width: 0; }
  /* What the message said. A message with no transcript draws nothing
     at all rather than an empty band. */
  /* What the message SAID, which is the message now. Quoted, because
     it is somebody's words and not our caption. border-inline-start
     and not border-left: the accent has to sit on the side the text
     starts on, and these callers speak a language that starts on the
     right. */
  /* A recorded call, read as a conversation. Bubbles rather than a
   paragraph, the caller down the left and you down the right, because
   the thing a reader wants from a transcript is to follow the exchange
   and a wall of text hides exactly that.

   The bubbles do NOT take the messages tab's colours: those mean "sent
   by me / received", a fact the system knows. This side is worked out
   from the words and can be wrong, so it stays in the quieter glass
   greys and the note underneath says as much. Confidence belongs to
   what was measured. */
.cd-chat { display: flex; flex-direction: column; gap: 10px; }
.chat-turn { display: flex; flex-direction: column; gap: 3px; max-width: 86%; }
.chat-turn.theirs { align-self: flex-start; }
.chat-turn.mine { align-self: flex-end; align-items: flex-end; }
.chat-who {
  font: 500 10.5px/1 var(--mono); letter-spacing: .7px;
  text-transform: uppercase; color: var(--text-2);
  padding-inline: 4px;
}
.chat-said {
  margin: 0; font: 400 14px/1.7 var(--ui); color: var(--text);
  padding: 9px 13px; border-radius: 14px;
  background: var(--glass); overflow-wrap: anywhere;
}
.chat-turn.mine .chat-said {
  background: var(--glass-2); border-bottom-right-radius: 5px;
}
.chat-turn.theirs .chat-said { border-bottom-left-radius: 5px; }
.chat-note {
  margin: 2px 0 0; font: 400 11.5px/1.5 var(--ui); color: var(--text-2);
  opacity: .85;
}

.vm-words {
    margin: 0; font: 400 14.5px/1.75 var(--ui); color: var(--text);
    white-space: pre-wrap; overflow-wrap: anywhere;
    padding: 12px 15px; border-radius: 10px; background: var(--glass);
    border-inline-start: 2px solid var(--primary);
  }
  .vm-words:empty { display: none; }
  .vm-actions { display: flex; gap: 8px; flex-wrap: wrap; }
  .vm-actions .btn { height: 32px; padding: 0 14px; font-size: 13px; }
  .vm-actions .spacer { flex: 1; }
  .vm-actions .btn.text.danger { color: var(--danger); }

  /* ---------- the sending cap, on the composer ---------- */
  .limitline {
    margin: 0 0 8px; padding: 8px 14px; border-radius: 12px;
    font-size: 12.5px; line-height: 1.4;
    background: var(--note-bg); color: var(--note-text);
  }
  .limitline.stop { background: var(--danger); color: #fff; }

  /* ---------- auto-replies ---------- */
  .ar-days { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 8px; }
  .ar-day {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px;
    padding: 8px 10px; border-radius: 12px; background: var(--glass);
  }
  .ar-day .day { width: 44px; font: 600 12px var(--mono); letter-spacing: .06em;
                 text-transform: uppercase; color: var(--text); }
  .ar-day label { display: inline-flex; align-items: center; gap: 6px;
                  font-size: 13px; color: var(--text-2); cursor: pointer; }
  .ar-day .times { display: inline-flex; align-items: center; gap: 8px;
                   margin-left: auto; }
  .ar-day input[type="time"] {
    height: 32px; padding: 0 6px; border: 1px solid var(--border);
    border-radius: 8px; background: var(--surface); color: var(--text);
    min-width: 0; width: 118px;
  }
  .ar-day .to { color: var(--text-2); font-size: 12px; }
  .ar-day.closed input[type="time"], .ar-day.closed .to { opacity: .35; }
  .ar-block { padding: 12px 0; border-bottom: 1px solid var(--border); }
  .ar-block > label { display: block; margin-bottom: 8px; }
  .ar-block b { display: block; font-weight: 500; }
  .ar-block > label span, .ar-hint { display: block; color: var(--text-2); font-size: 13px; }
  .ar-block textarea, .ar-block > input {
    width: 100%; padding: 10px 12px; border: 1px solid var(--border);
    border-radius: 8px; background: var(--surface); color: var(--text);
    outline: none; font: inherit; resize: vertical;
  }
  .ar-block textarea:focus, .ar-block > input:focus { border-color: var(--primary); }
  .ar-keywords { display: flex; flex-direction: column; gap: 8px; margin: 10px 0 4px; }
  .ar-kw { display: flex; gap: 8px; align-items: center; }
  .ar-kw input {
    height: 36px; padding: 0 10px; border: 1px solid var(--border);
    border-radius: 8px; background: var(--surface); color: var(--text); outline: none;
    min-width: 0;
  }
  .ar-kw input:focus { border-color: var(--primary); }
  .ar-kw .kw-word { width: 120px; flex: none; font-family: var(--mono); text-transform: uppercase; }
  .ar-kw .kw-reply { flex: 1; }
  .ar-actions { display: flex; justify-content: flex-end; padding-top: 16px; }
  #ar-form.readonly input, #ar-form.readonly textarea, #ar-form.readonly .switch,
  #ar-form.readonly button { pointer-events: none; opacity: .7; }
  #ar-form.readonly .ar-actions, #ar-form.readonly #ar-kw-add,
  #ar-form.readonly .ar-kw .icon-btn { display: none; }

  /* ---------- blocked numbers ---------- */
  .blk-add { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 14px 0 6px; }
  .blk-list { display: flex; flex-direction: column; gap: 8px; padding-top: 8px; }
  .blk-row {
    display: flex; align-items: center; gap: 12px; padding: 10px 14px;
    border-radius: 14px; background: var(--glass);
  }
  .blk-row .who { flex: 1; min-width: 0; }
  .blk-row .who b { display: block; font: 500 14px var(--mono); }
  .blk-row .who span { display: block; color: var(--text-2); font-size: 12.5px;
                       overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* ---------- passkeys ---------- */
  .pk-list { display: flex; flex-direction: column; gap: 8px; padding: 4px 0 12px; }
  .pk-row {
    display: flex; align-items: center; gap: 12px; padding: 10px 14px;
    border-radius: 14px; background: var(--glass);
  }
  .pk-row .who { flex: 1; min-width: 0; }
  .pk-row .who b { display: block; font-weight: 500; }
  .pk-row .who span { display: block; color: var(--text-2); font-size: 12.5px; }
  .pk-add { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 4px 0 12px; }
  /* Sign in, and the way out of it. Wide enough, they sit on one line
     with the primary action on the right; on a phone they stack, the
     primary action first and full width and the link centred under it —
     which is Android's order (SignInScreen.kt:228-246) and cures a
     "Sign in" that wrapped onto two lines at 390px. */
  .signin-actions { display: flex; align-items: center; gap: 12px; }
  .signin-actions .btn { white-space: nowrap; }
  .signin-actions #signin-go, .signin-actions #devices-go { margin-left: auto; }
  @media (max-width: 460px) {
    .signin-actions { flex-direction: column; align-items: stretch; gap: 14px; }
    .signin-actions #signin-go, .signin-actions #devices-go {
      order: -1; margin-left: 0; width: 100%; justify-content: center;
    }
    .signin-actions #forgot-go, .signin-actions #devices-back { justify-content: center; }
  }
  /* At the device limit: the person's devices of that kind, one to pick. */
  .dev-choices { display: flex; flex-direction: column; gap: 8px; margin: 0 0 24px; }
  .dev-choice {
    display: flex; align-items: flex-start; gap: 12px; padding: 12px 14px;
    border-radius: 14px; background: var(--glass); border: 1px solid transparent;
    cursor: pointer;
  }
  .dev-choice:has(input:checked) { border-color: var(--primary); background: var(--primary-soft); }
  .dev-choice input { margin: 3px 0 0; accent-color: var(--primary); flex: none; }
  /* A device as a list shows it — here and in Settings → Security. Its
     own class: the list rows' .who keeps to one line, and a phone's
     model and system do not fit on one. */
  .dev-who { flex: 1; min-width: 0; }
  .dev-who b { display: block; font-weight: 500; overflow-wrap: anywhere; }
  .dev-who span { display: block; color: var(--text-2); font-size: 12.5px; overflow-wrap: anywhere; }

  .passkey-row { display: flex; align-items: center; gap: 12px; margin-top: 22px; }
  .passkey-row .or { color: var(--text-2); font-size: 13px; }
  .passkey-row .btn svg { width: 18px; height: 18px; }
  /* The Google row sits under the passkey row when both are offered:
     one "or" between the form and the pair, not two. */
  .passkey-row.google-row { margin-top: 12px; }
  #signin-passkey-row:not(.hidden) ~ .google-row .or { visibility: hidden; }
  .google-row .btn { color: var(--text); text-decoration: none; }
  a.btn { text-decoration: none; }
  /* Google's second step: the authenticator code alone. */
  #signin-form.google-step .field:not(#totp-field),
  #signin-form.google-step #forgot-go,
  #signin-form.google-step .passkey-row { display: none; }
  #google-back { margin-top: 18px; }

  /* ---------- Google Contacts, in Settings → Contacts ---------- */
  .setting .label .gc-status { display: block; margin-top: 6px; color: var(--text); font-size: 13px; }
  .gc-status.bad { color: var(--danger); }
  .gc-control { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
  .gc-ask {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    padding: 12px 0 16px; border-bottom: 1px solid var(--border); font-size: 13px;
  }
  .gc-ask span { flex: 1; min-width: 200px; color: var(--text-2); }
  /* On a phone the two buttons take a row of their own under the words. */
  @media (max-width: 839px) {
    #gc-setting { flex-wrap: wrap; gap: 12px 24px; }
    #gc-setting .gc-control { width: 100%; justify-content: flex-start; }
  }

  /* ---------- the contact sheet's head: the star and the block ---------- */
  .ct-flags { display: inline-flex; gap: 8px; }
  .field textarea {
    width: 100%; padding: 13px 15px; background: transparent;
    border: 1px solid var(--border); border-radius: 4px; outline: none;
    resize: vertical; min-height: 64px; font: inherit;
  }
  .field textarea:focus { border-color: var(--primary); box-shadow: inset 0 0 0 1px var(--primary); }
  .field textarea:focus + label { color: var(--primary); }

  /* ---------- one number's story (call detail) ---------- */
  .cd-top { flex: none; padding: 8px 6px 0; }
  #cd-body { padding-bottom: 24px; }
  .cd-hero {
    display: flex; flex-direction: column; align-items: center;
    padding: 4px 20px 18px;
    text-align: center;
  }
  .cd-hero .avatar {
    width: 84px; height: 84px; border-radius: 50%;
    display: grid; place-items: center;
    color: #fff; font-size: 34px; font-weight: 500;
  }
  .cd-hero b { font-size: 24px; font-weight: 700; margin-top: 12px; }
  .cd-hero span { font-size: 14px; color: var(--text-2); margin-top: 3px; }
  .cd-tiles { display: flex; gap: 14px; margin-top: 16px; }
  .cd-tile {
    display: flex; flex-direction: column; align-items: center; gap: 6px;
    border: 0; background: transparent; cursor: pointer;
    color: var(--text-2); font-size: 12px;
  }
  .cd-tile .tile {
    width: 60px; height: 60px; border-radius: 20px;
    background: var(--glass);
    display: grid; place-items: center;
    color: var(--text);
  }
  .cd-tile.prime .tile { background: var(--primary); color: var(--on-primary); }
  .cd-tile:hover .tile { background: var(--hover); }
  .cd-tile.prime:hover .tile { background: var(--primary); }
  .cd-stats { font-size: 13px; color: var(--text-2); margin-top: 14px; }
  .cd-row {
    display: flex; gap: 12px; align-items: center;
    padding: 8px 20px;
  }
  .cd-row .glyph {
    width: 40px; height: 40px; border-radius: 50%; flex: none;
    background: var(--glass);
    display: grid; place-items: center;
    color: var(--text-2);
  }
  .cd-row .glyph svg { width: 18px; height: 18px; }
  .cd-row.missed .glyph { color: var(--danger); }
  /* A call's recording, indented under its row's text. */
  .cd-rec { padding: 0 20px 10px 72px; max-width: 560px; }

  /* The one call that is OPEN: its own panel, lifted off the list so it
     reads as the thing on the page rather than the first of twelve. */
  .cd-open {
    margin: 4px 12px 8px;
    border: 1px solid var(--line);
    border-radius: 14px;
    background: var(--glass);
    padding-top: 4px;
    overflow: hidden;
    /* `overflow: hidden` rounds the panel's corners — and, on a flex
       item, ALSO sets its automatic minimum size to zero. So the column
       this sits in was free to squeeze the open call down to whatever
       was left over and clip the rest, which is what it did: a read
       call's summary ended mid-sentence and the pane had nothing to
       scroll, because nothing overflowed it. `flex: none` keeps the
       panel at its own height; the pane overflows and scrolls, which is
       what the reader was reaching for. */
    flex: none;
  }
  .cd-open .cd-open-head {
    display: flex; gap: 12px; align-items: center; padding: 8px 16px 4px;
  }
  .cd-open .cd-open-head .glyph {
    width: 40px; height: 40px; border-radius: 50%; flex: none;
    background: var(--hover);
    display: grid; place-items: center; color: var(--text-2);
  }
  .cd-open .cd-open-head .glyph svg { width: 18px; height: 18px; }
  .cd-open .who { min-width: 0; }
  .cd-open .who b { display: block; font-size: 15px; font-weight: 600; }
  .cd-open .who span { font-size: 12.5px; color: var(--text-2); }
  .cd-open .cd-rec { padding: 0 16px 12px 68px; }
  .cd-open .cd-none { padding: 0 16px 14px 68px; }

  /* An earlier call: a line, and a button, because tapping it brings it
     up into the panel above. Never more than a line. */
  .cd-earlier {
    width: calc(100% - 24px); margin: 0 12px; text-align: left;
    background: none; border: 0; border-radius: 10px;
    color: inherit; font: inherit; cursor: pointer;
  }
  .cd-earlier:hover { background: var(--hover); }
  .cd-taped {
    flex: none; font-size: 11px; color: var(--text-2);
    border: 1px solid var(--line); border-radius: 999px; padding: 1px 7px;
  }
  .daylabel.sub { font-size: 11.5px; opacity: .75; padding-top: 2px; }
  .cd-tile.on .tile { background: var(--primary-soft); color: var(--star); }
  .cd-tile.blocked .tile { background: var(--danger); color: #fff; }
  .cd-notes {
    font-size: 13px; color: var(--text-2); margin-top: 8px; max-width: 420px;
    white-space: pre-wrap; overflow-wrap: anywhere;
  }
  .cd-row .who { flex: 1; min-width: 0; }
  .cd-row .who b { display: block; font-size: 14.5px; font-weight: 500; }
  .cd-row .who span { font-size: 12.5px; color: var(--text-2); }
  .cd-row time { font-size: 12px; color: var(--text-2); }

  /* ---------- contacts ---------- */
  .letterhead {
    padding: 14px 20px 4px;
    font-size: 11px; font-weight: 600; letter-spacing: .1em;
    color: var(--text-2);
  }
  /* Voicemail's empty state is .nothing now, like the other three —
     these rules went with the markup it used to have. */
  /* A quiet destructive action — "Remove", "Delete" — is red TEXT, not a
     red button: the solid .btn.danger rule above would otherwise paint
     red words on a red block and the label would vanish. */
  .btn.text.danger { background: transparent; color: var(--danger); }
  .btn.text.danger:hover:not(:disabled) { background: var(--hover); }

  /* The phone tabs read like the app on a phone; on a desktop their
   * content holds a comfortable column instead of stretching. */
  .pane-head, .chiprow, #calls-note, #vm-note, .phonepane .rows, .cd-top, #cd-body {
    width: 100%; max-width: 880px;
    margin-left: auto; margin-right: auto;
  }
  #vm-note, #calls-note { margin-top: 8px; width: calc(100% - 24px); }
  #cd-body { overflow-y: auto; }
  .phonepane .rows { overflow-y: auto; }
  @media (max-width: 839px) {
    body.viewing nav.bottomnav { display: none; }
    body.composing nav.bottomnav { display: none; }
    body.settings-open nav.bottomnav { display: none; }
  }


/* Responsive workspace. The phone keeps Android's floating pill; desktop
   uses the same controls in a compact floating sidebar. */
#app { height: 100dvh; min-height: 0; position: relative; }
/* A workspace with ONE door (texting only, say) has nothing to navigate
   between: no pill at the foot, no column at the side (applyFeatures). */
body.onedoor nav.bottomnav { display: none !important; }
@media (min-width: 840px) {
  body.onedoor #app { grid-template-columns: minmax(0, 1fr); }
  body.onedoor #app > .body { grid-column: 1; margin-left: 16px; }
  body.onedoor .minicall { grid-column: 1; }
}
/* Voicemail is an ordinary nav door now, so it takes the nav's own rules
   and nothing of its own: this used to be a labelled pill in the Recents
   header, and an id-level background here beat `nav.bottomnav
   button[aria-selected="true"]`, leaving the lit door grey. */
body.tab-keypad #keypadpane { display: flex; }
body.tab-keypad section.list, body.tab-keypad section.thread { display: none; }
#keypadpane { overflow-y: auto; }
.keypad-layout { width: 100%; max-width: 1040px; margin: 0 auto; display: flex; gap: 32px; padding: 16px 28px 28px; }
#dialpad {
  position: static; display: flex; flex: 0 1 400px; width: 100%; height: auto;
  min-height: 570px; max-height: none; margin: 0; border-radius: 28px;
  background: var(--surface); color: var(--text); border: 1px solid var(--border);
}
#dialpad .dial-display { min-height: 116px; }
#dialpad .dial-num { color: var(--text); font-size: clamp(26px, 7vw, 38px); }
#dialpad .dial-sub { color: var(--text-2); }
#dialpad .dial-key { background: var(--glass-2); color: var(--text); height: 64px; }
#dialpad .dial-key small { color: var(--text-2); font-size: 10px; }
#dialpad .dial-foot { padding: 22px 0 28px; }
#dialpad .dial-side { width: 50px; }
#dialpad .dial-back svg, #dial-back svg { fill: var(--text-2); }
.keypad-recents { flex: 1; min-width: 0; padding-top: 8px; }
.keypad-recents h2 { font-size: 18px; margin: 0 0 16px; }
.keypad-recents .call-head { padding-left: 0; padding-right: 0; }
.keypad-recents .callrow::after { left: 53px; right: 0; }
.keypad-recents #keypad-recents { color: var(--text-2); }
.minicall { background: var(--nav-surface); border: 1px solid var(--border); border-radius: 18px;
  display: flex; align-items: center; gap: 10px; padding: 8px; margin: 0 16px 16px 0; }
.minicall button { cursor: pointer; border: 0; border-radius: 12px; min-height: 44px; padding: 8px 14px; background: var(--glass); }
#minicall-open { flex: 1; min-width: 0; display: flex; align-items: center; gap: 12px; text-align: left; background: transparent; }
#minicall-open > span:nth-child(2) { flex: 1; min-width: 0; }
#minicall-open b { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#minicall-status { display: block; color: var(--text-2); font: 12px var(--mono); }
.minicall-dot { width: 9px; height: 9px; background: var(--primary); border-radius: 50%; flex: none; }
.minicall .minicall-end { background: var(--danger); color: #fff; }
.minimize-call { align-self: flex-end; margin: 0 0 10px; }
/* The list column's handle exists on a desktop alone; a phone has one
   pane at a time. */
#col-resizer { display: none; }
@media (min-width: 840px) {
  #app { display: grid; grid-template-columns: 136px minmax(0,1fr); grid-template-rows: auto minmax(0,1fr) auto; }
  /* The edge between the list and the pane beside it is a handle: drag it
     and --list follows (kept in this browser; double-click resets). One
     variable drives every two-pane tab and where the keypad floats, so
     they all move together. Twelve pixels wide to grab, a two-pixel line
     to see, lit while hovered, focused or dragged. Settings has no list
     column, so no handle there. */
  #app > .body { position: relative; }
  #col-resizer { display: block; position: absolute; top: 0; bottom: 0; left: calc(var(--list) - 6px);
    width: 12px; z-index: 6; cursor: col-resize; touch-action: none; }
  #col-resizer::after { content: ""; position: absolute; top: 0; bottom: 0; left: 5px; width: 2px;
    background: transparent; transition: background .12s; }
  #col-resizer:hover::after, #col-resizer:focus-visible::after, body.resizing #col-resizer::after {
    background: var(--primary); }
  #col-resizer:focus-visible { outline: none; }
  body.settings-open #col-resizer { display: none; }
  body.resizing, body.resizing * { cursor: col-resize !important; user-select: none; }
  #app > header.bar { grid-column: 1 / -1; padding: 18px 24px; gap: 18px; }
  #app > header.bar::before { content: "Sonaline"; font-size: 22px; font-weight: 600; letter-spacing: -.6px; }
  #app > .body { grid-column: 2; grid-row: 2; border: 1px solid var(--border); border-radius: 24px; overflow: hidden; margin: 0 16px 16px 0; background: var(--surface); }
  nav.bottomnav { grid-column: 1; grid-row: 2 / 4; flex-direction: column; align-self: start; margin: 0 16px; border-radius: 28px; }
  nav.bottomnav button { flex: none; min-height: 70px; border-radius: 22px; padding: 12px 6px; }
  nav.bottomnav .nav-label { font-size: 12px; }
  .searchfield, body.searching .searchfield { display: flex; align-items: center; position: static; order: 1;
    max-width: 440px; flex: 1; height: 44px; padding: 0 12px; margin-left: auto; gap: 8px;
    background: var(--glass); border: 1px solid var(--border); border-radius: 999px; }
  .bar-right { order: 2; }
  body.searching .linechip, body.searching .bar-right { visibility: visible; }
  #search-open, #search-cancel { display: none; }
  .minicall { grid-column: 2; grid-row: 3; }
  /* The phone tabs are two panes, as Messages is: the list at the left
     in the conversation list's own column, and at the right the number's
     story — or, until a row is picked, a word about what goes there. A
     story keeps the list it was opened from beside it (the body's
     data-detail-from, set by showCallDetail), so a contact's story
     stands by the contacts and a voicemail's caller by the voicemail. */
  body.tab-calls #callspane, body.tab-detail[data-detail-from="calls"] #callspane,
  body.tab-contacts #contactspane, body.tab-detail[data-detail-from="contacts"] #contactspane,
  body.tab-voicemail #vmpane, body.tab-detail[data-detail-from="voicemail"] #vmpane {
    display: flex; flex: none; width: var(--list);
    border-right: 1px solid var(--border);
  }
  body.tab-calls #calldetail, body.tab-contacts #calldetail, body.tab-voicemail #calldetail { display: flex; }
  /* The story pane is the right-hand one whichever list it follows in
     the markup (the contacts and voicemail panes come after it there). */
  #calldetail { order: 1; }
  #callspane .pane-head, #vmpane .pane-head { flex-wrap: wrap; gap: 8px; }
  #callspane .chiprow { flex-wrap: wrap; }
  /* Beside its list the story needs no back arrow, as a conversation
     has none; and while no row is picked the pane says what it is for. */
  #calldetail .cd-top { display: none; }
  body:not(.tab-detail) #cd-body { display: none; }
  body:not(.tab-detail) #cd-empty { display: flex; }
  .thread-head { min-height: 76px; }
}
@media (min-width: 840px) and (max-width: 1100px) {
  #app { grid-template-columns: 108px minmax(0,1fr); }
  nav.bottomnav { margin: 0 10px; }
  .keypad-layout { gap: 16px; padding: 12px 18px; }
  .keypad-recents .callrow time { display: none; }
}
@media (max-width: 839px) {
  #contact-dlg { width: 100%; max-width: 100%; max-height: 92dvh; margin: auto 0 0; border-radius: 28px 28px 0 0; border: 0; }
  #contact-dlg .dlg { padding-bottom: max(24px, env(safe-area-inset-bottom)); }
  /* 320px was the four-door cap, the phone dialer's own proportion
     (HomeNavBar's widthIn(max = 320.dp)). A line with voicemail gets a
     fifth door and the cap follows the count, as the app's does. */
  nav.bottomnav { position: absolute; z-index: 5; left: 50%; transform: translateX(-50%);
    bottom: max(12px, env(safe-area-inset-bottom)); width: calc(100% - 48px); max-width: 246px; margin: 0; }
  nav.bottomnav:has(#nav-voicemail:not(.hidden)) { max-width: 318px; }
  nav.bottomnav[data-doors="2"] { max-width: 174px; }
  /* One door: no nav, and the room it kept free at the foot goes back. */
  body.onedoor section.list > .rows, body.onedoor .phonepane .rows { padding-bottom: env(safe-area-inset-bottom); }
  body.onedoor #list .fab, body.onedoor .phonepane .fab { bottom: calc(16px + env(safe-area-inset-bottom)); }
  section.list > .rows, .phonepane > .rows { padding-bottom: calc(104px + env(safe-area-inset-bottom)); }
  #list .fab, .phonepane .fab { bottom: calc(104px + env(safe-area-inset-bottom)); }
  .keypad-layout { padding: 0 12px calc(104px + env(safe-area-inset-bottom)); justify-content: center; }
  .keypad-recents { display: none; }
  #dialpad { flex-basis: 400px; min-height: 530px; border: 0; background: transparent; }
  #keypadpane > .pane-head { padding-bottom: 0; }
  #dialpad .dial-display { min-height: 92px; }
  .minimize-call, .minicall { display: none !important; }
  .composer { padding-bottom: max(14px, env(safe-area-inset-bottom)); }
  .pane-head { flex-wrap: wrap; gap: 8px; }
  .pane-tools { flex-wrap: wrap; }
  .icon-btn, .avatar-btn { min-width: 44px; min-height: 44px; }
  input, textarea, select { font-size: max(16px, 1em); }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}
/* Flex children must shrink below the textarea's intrinsic width. */
.composer .inputwrap, .composer textarea, .thread-head .headtext { min-width: 0; }
.composer textarea { width: 100%; }

/* THE PALETTE LIVES IN design.css AND NOWHERE ELSE.
 *
 * A second copy of it stood here — "Values from Android Theme.kt, scoped
 * to the app stylesheet" — declared AFTER design.css and therefore
 * winning, on ten custom properties. Six agreed with design.css and were
 * merely redundant. Four did not:
 *
 *     --primary  light   #1b8a5a   against design.css's #1b7f57
 *     --glass    light   rgba(0,0,0,.047)   against #0000000F
 *     --glass    dark    rgba(255,255,255,.07)  against #FFFFFF0F
 *     --surface  light   #f6f7f9   against #ffffff
 *
 * The first is the one that matters. design/tokens.json names #1B8A5A
 * specifically as the value NOT to use — "Android's #1B8A5A gives white
 * text 4.35:1, under the 4.5:1 AA needs for normal text" — and the web's
 * #1B7F57 is why the token file exists. This block put the rejected value
 * back, so the lit navigation door and every outbound bubble have been
 * rendering at 4.35:1 while design.css, tokens.json and the token check
 * all said 4.98:1. The check reads design.css and Theme.kt; it had no
 * idea app.css was overriding them afterwards.
 *
 * design/tokens.mjs now refuses any redeclaration of a shared token
 * outside design.css, so this cannot come back quietly.
 */

/* Screen parity with Android: grouped settings, a flat keypad with its
   history above the keys, contact detail actions and compact voicemail. */
.scene-label { font: 500 10px var(--mono); text-transform: uppercase; letter-spacing: 1.4px; color: var(--faint, var(--text-2)); padding: 14px 8px 6px; margin: 0; }
/* The same sheet the four lists are drawn on — `GroupCard`, whose own
   comment says Settings was the one page still wearing grey cards once
   the lists went white (SettingsScreen.kt:2462). */
.settings-group { border-radius: 20px; background: var(--card); overflow: hidden; margin-bottom: 4px; }
.settings-group .cat-row { margin: 0; border-radius: 0; border: 0; background: transparent; padding: 11px 16px; min-height: 66px; position: relative; }
.settings-group .cat-row + .cat-row::before { content: ""; position: absolute; left: 66px; right: 0; top: 0; height: 1px; background: var(--border); }
/* The small round behind a settings icon: web.circle, which is what
   that token is FOR (`Box(Modifier.size(36.dp).background(web.circle,
   CircleShape))`, SettingsScreen.kt:2485) — and on a white card, glass
   and circle are not the same grey. */
.settings-group .cat-ic { width: 36px; height: 36px; border-radius: 50%; background: var(--circle); }
#settings-home .cat-card { border-radius: 20px; background: var(--card); }
.account-initial { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%; background: var(--primary); color: var(--on-primary); font-size: 16px; }
.settings-cat .group { border-radius: 20px; }
.settings-cat .cat-head { position: sticky; top: 0; background: var(--surface); z-index: 1; padding-top: 8px; padding-bottom: 8px; }
.settings .inner { max-width: 760px; }
.cat-back { min-width: 44px; min-height: 44px; }
.parity-field { display: flex; flex-direction: column; gap: 8px; font-size: 14px; margin: 14px 0; }
.parity-field input { width: 100%; min-width: 0; padding: 12px; min-height: 46px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); color: var(--text); }
#line-settings > .group, #carrier-settings > .group { padding: 16px; margin-top: 16px; }
.line-description, .mail-address { overflow-wrap: anywhere; }
.mail-address { font: 13px var(--mono); user-select: all; }
.managed-line { display: flex; gap: 10px; align-items: center; border-bottom: 1px solid var(--border); }
.line-open { flex: 1; min-width: 0; background: transparent; border: 0; padding: 16px 0; text-align: left; cursor: pointer; color: var(--text); }
.line-open b, .line-open span { display: block; overflow-wrap: anywhere; }
.line-open span { font-size: 13px; color: var(--text-2); margin-top: 4px; }
.line-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.about-brand { font-size: 36px; font-weight: 600; color: var(--primary); margin-top: 24px; }
/* Appearance: a list with a radio per row, as the phone app draws it. */
.choice-list { display: flex; flex-direction: column; width: 100%; }
.choice-list button { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; padding: 14px 42px 14px 0; text-align: left; position: relative; border: 0; border-bottom: 1px solid var(--border); background: transparent; color: var(--text); font: inherit; cursor: pointer; }
.choice-list button b { font-size: 14px; font-weight: 500; }
.choice-list button small { font-size: 13px; color: var(--text-2); white-space: normal; }
.choice-list button::after { content: ""; width: 18px; height: 18px; border: 2px solid var(--text-2); border-radius: 50%; position: absolute; right: 8px; top: 24px; }
.choice-list button[aria-pressed="true"]::after { border-color: var(--primary); background: radial-gradient(var(--primary) 0 45%, transparent 48%); }
#line-settings .switch { flex-shrink: 0; }
/* Do Not Disturb keeps its corner, and nothing else is up here — the
   app puts a Spacer to its left and that is the whole row
   (CallScreen.kt:2432-2440). */
.keypad-tools { display: flex; align-items: center; justify-content: flex-end; width: 100%; max-width: 1040px; margin: 0 auto; padding: 0 24px; }
/* Do Not Disturb is a WARNING while it is on, not a lit setting: the
   app tints the glyph and the chip beside it with web.missed
   (`DndToggle`, CallsScreen.kt:127-131). This used to go accent-green on
   accent-soft, which read as "everything is fine" for the one state on
   this screen that means calls are not getting through. */
#calls-dnd[aria-pressed="true"] { color: var(--missed); }
/* The clipboard offer, in the app's shape: centred over the keys, the
   accent at 14% behind it, the glyph and the words in the accent
   (CallScreen.kt:2576-2588). */
.dial-paste-row { display: flex; justify-content: center; padding-bottom: 10px; }
.dial-paste {
  display: inline-flex; align-items: center; gap: 6px;
  border: 0; cursor: pointer; border-radius: 999px;
  padding: 7px 12px;
  background: color-mix(in srgb, var(--primary) 14%, transparent);
  color: var(--primary);
  font: 500 12px var(--ui);
}
.dial-paste svg { width: 14px; height: 14px; fill: currentColor; flex: none; }
.dial-paste:hover { background: color-mix(in srgb, var(--primary) 22%, transparent); }
#dialpad { border: 0; border-radius: 0; background: transparent; min-height: 0; padding: 0; }
#dialpad .dial-keys { gap: 0; padding: 0; margin: 0 24px; width: auto; border: 0; }
#dialpad .dial-key { border: 0; border-radius: 0; background: transparent; height: 62px; border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); }
#dialpad .dial-key:nth-child(3n) { border-right: 0; }
#dialpad .dial-key:nth-child(n+10) { border-bottom: 0; }
#dialpad .dial-key:hover { background: var(--hover); }
#dialpad .dial-key > span { font-size: 30px; font-weight: 400; }
#dialpad .dial-foot { padding: 14px 0 8px; }
#dialpad .dial-call { width: 72px; height: 72px; }
#dialpad .dial-call { background: var(--primary); color: var(--on-primary); box-shadow: none; }
#dialpad .dial-num { text-align: center; }
#dialpad .dial-sub { text-align: center; }
.parked-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 6px 24px; padding: 10px 14px; border-radius: 16px; background: var(--primary-soft); }
.parked-bar > span { min-width: 0; overflow-wrap: anywhere; }
#advanced-call-sheet { padding: 18px; }
.glass-tile:disabled { opacity: .4; cursor: default; }
.more-call-button { display: none; }
.compact-call .more-call-button { display: block; color: #f2f4f8; margin: 4px 0 8px; }
.compact-call #incall-mute, .compact-call #incall-keypad, .compact-call #incall-speaker, .compact-call #incall-hold { min-width: 0; }
.compact-call #ctrl-row-1 { grid-template-columns: repeat(4, 1fr); }
#more-call-sheet { padding: 18px; }
/* The third control row needs a compact call scene on short screens. */
@media (max-height: 740px) {
  .incall-scene { overflow-y: auto; padding: 12px 20px; }
  .incall-id { flex: 0 0 auto; padding: 8px 0 14px; }
  .incall-avatar { width: 56px; height: 56px; margin-bottom: 8px; }
  .incall-avatar svg { width: 30px; height: 30px; }
  .incall-name { font-size: 24px; }
  .tile-box { height: 44px; border-radius: 16px; }
  .tile-row { margin-bottom: 8px; }
  .glass-tile { gap: 4px; }
  .end-pill { width: 56px; height: 56px; margin-top: 4px; }
  .call-sheet { max-height: 90%; overflow-y: auto; }
}
#dialpad .dial-display { min-height: 76px; padding: 6px 14px; }
#dialpad .dial-num { font-size: 36px; font-weight: 300; }
#dialpad .dial-sub { min-height: 16px; font-size: 12px; }
/* A match for the digits typed, measure for measure from the phone's
   `SuggestRow` (CallScreen.kt:2026): 40px circle, 14px gap, the name —
   or the raw digits when there is no name — at body size on the left,
   and THE NUMBER WRITTEN OUT ON THE RIGHT rather than under the name, so
   that reading down the list the numbers line up in a column of their
   own. That column is how two of somebody's numbers are told apart. */
.dial-match { display: flex; align-items: center; min-width: 0; gap: 8px; position: relative; }
.dial-match-call { border: 0; background: transparent; display: flex; align-items: center; gap: 14px; padding: 11px 0; text-align: left; min-width: 0; flex: 1; cursor: pointer; }
.dial-match-call b, .dial-match-call small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dial-match-call b { font: 400 15px/22px var(--ui); color: var(--text); flex: 1; min-width: 0; }
.dial-match-call small { font: 400 13px/18px var(--ui); color: var(--text-2); flex: none; }
/* The part that was typed, lit — `litRange` / `litDigits`, which is what
   says WHY a row is in the list at all. */
.dial-match .lit { color: var(--primary); font-weight: 600; }
.dial-match .avatar { width: 40px; height: 40px; flex: none; }
.dial-match::after {
  content: ""; position: absolute; left: 54px; right: 0; bottom: 0;
  height: 1px; background: var(--border); pointer-events: none;
}
.dial-match:last-child::after { display: none; }
.dial-match:hover { background: var(--hover); border-radius: 12px; }
#keypad-recents { overflow-y: auto; min-height: 0; }
.keypad-recents { display: flex; flex-direction: column; }
.keypad-recents h2 { font: 500 10px var(--mono); letter-spacing: 1.4px; text-transform: uppercase; }
.contactrow { cursor: pointer; text-align: left; }
.avatar.initials { display: grid; place-items: center; font-size: 15px; color: #eef1f5; font-weight: 500; }
.contactrow:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
#cd-body .cd-hero .avatar { width: 84px; height: 84px; font-size: 30px; }
.cd-tiles { gap: 14px; justify-content: center; flex-wrap: wrap; }
.cd-tile { padding: 0; }
.cd-tile .tile { width: 50px; height: 50px; border-radius: 16px; }
.vmrow { padding: 0 12px 0 20px; background: transparent; border: 0; position: relative; }
.vm-head { display: flex; width: 100%; border: 0; background: transparent; text-align: left; align-items: center; color: var(--text); padding: 12px 0; cursor: pointer; }
.vm-head[aria-expanded="true"] { color: var(--primary); }
.vm-head .avatar { width: 40px; height: 40px; flex: none; }
.vm-play, .vm-words, .vm-actions { padding: 0 0 12px 53px; }
.vm-words { margin-inline-end: 12px; }
.vm-play .listen { width: 100%; }
@media (min-width: 840px) {
  .keypad-layout { height: 100%; min-height: 0; align-items: center; }
  .keypad-recents { align-self: stretch; max-height: 100%; padding: 24px 0; }
  #dialpad { flex-shrink: 0; max-width: 400px; }
  #keypad-recents { flex: 1; }

  /* On a desktop the keypad is not a page: the pad floats over whichever
     tab is open (body.pad-open), from the Keypad door or Recents' green
     button, so a number can be dialed with a call or a contact still on
     screen. Its own list shows only while typing — the matches — since
     Recents is a door away. On a phone the keypad stays the full-screen
     tab it is in the app. */
  body.pad-open #keypadpane {
    display: flex; position: fixed; right: 28px; bottom: 28px; z-index: 20;
    width: 380px; height: auto; max-height: calc(100dvh - 150px);
    border-radius: 28px; background: var(--surface); border: 1px solid var(--border);
    box-shadow: 0 24px 64px rgba(0,0,0,.30); overflow: hidden;
  }
  /* Above the minimized call's bar when one is showing. */
  body.pad-open:has(#minicall:not(.hidden)) #keypadpane { bottom: 104px; }
  body.pad-open #keypadpane .keypad-tools, body.pad-rest #keypadpane .keypad-tools { padding: 10px 14px 0; max-width: none; }
  body.pad-open #pad-close, body.pad-rest #pad-close { display: inline-grid; }
  body.pad-open .keypad-layout, body.pad-rest .keypad-layout { flex-direction: column; align-items: stretch; gap: 0; height: auto; min-height: 0; max-width: none; padding: 0 12px 12px; overflow: hidden; }
  body.pad-open .keypad-recents, body.pad-rest .keypad-recents { display: none; order: -1; flex: none; align-self: stretch; padding: 0; max-height: 196px; }
  body.pad-open #keypadpane.has-query .keypad-recents, body.pad-rest #keypadpane.has-query .keypad-recents { display: flex; }
  body.pad-open .keypad-recents h2, body.pad-rest .keypad-recents h2 { margin: 0; padding: 8px 8px 2px; }
  body.pad-open #keypad-recents, body.pad-rest #keypad-recents { flex: 1; overflow-y: auto; }
  body.pad-open #keypad-all-calls, body.pad-rest #keypad-all-calls { display: none; }
  body.pad-open #dialpad, body.pad-rest #dialpad { flex: none; width: 100%; max-width: none; min-height: 0; border: 0; border-radius: 0; background: transparent; }
  body.pad-open #dialpad .dial-display, body.pad-rest #dialpad .dial-display { min-height: 76px; }
  body.pad-open #dialpad .dial-key, body.pad-rest #dialpad .dial-key { height: 56px; }
  body.pad-open #dialpad .dial-foot, body.pad-rest #dialpad .dial-foot { padding: 12px 0 8px; }
  body.pad-open #go-dial { display: none !important; }
  /* The Keypad door is the pad's switch on a desktop: lit while it is open. */
  /* The rail's lit door, the same accent as the pill's (above). */
  nav.bottomnav button[aria-pressed="true"] {
    background: var(--primary);
    color: var(--on-primary); font-weight: 600;
  }
}
#pad-close { display: none; }
/* The keypad's way in from Recents, at every width — it is now the
   ONLY way in, the nav door having gone. Hidden only while the pad is
   already up, because then it points at what is on screen. */
body.pad-open #go-dial { display: none; }
.pad-tools-end { display: inline-flex; align-items: center; gap: 8px; }
@media (max-width: 839px) {
  #keypadpane { overflow: hidden; }
  .keypad-tools { padding: 0 14px; flex: none; }
  .keypad-layout { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 0; justify-content: flex-end; padding: 0 14px calc(94px + env(safe-area-inset-bottom)); }
  .keypad-recents { display: flex; order: -1; flex: 1; min-height: 0; width: 100%; max-width: 600px; margin: 0 auto; padding-top: 0; }
  /* No heading over the log on a phone: the app's keypad has no title at
     all — "No title: the number and the keys are the page"
     (CallScreen.kt:2429) — and this is the width where the web's keypad
     IS the app's, the log stacked straight over the pad. Wider, the log
     becomes a panel beside the pad, which the app has no counterpart for
     and which needs to say what it is; there the heading stays. The
     screen reader keeps it at every width: the <aside> is labelled. */
  .keypad-recents h2 { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  #keypad-all-calls { display: none; }
  #keypad-recents { flex: 1; }
  #dialpad { flex: 0 0 auto; width: 100%; max-width: 480px; align-self: center; }
  #dialpad .dial-key { height: clamp(42px, 7.5dvh, 64px); }
  #dialpad .dial-num { font-size: clamp(26px, 8vw, 36px); }
  #dialpad .dial-rec { margin: 2px 0; font-size: 10px; }
  .settings .inner { padding: 8px 14px 32px; }
  .settings .setting { gap: 12px; }
  #line-settings .setting { flex-direction: row; align-items: center; }
  #line-settings .setting .label { flex: 1; min-width: 0; }
  .settings .control { max-width: 100%; }
  .settings select { max-width: 100%; }
  .vm-play, .vm-actions { padding-left: 0; }
  body.tab-detail .bar, body.tab-detail nav.bottomnav { display: none; }
  body.settings-open .bar, body.viewing .bar, body.composing .bar { display: none; }
  body.tab-detail #calldetail .rows { padding-bottom: max(24px, env(safe-area-inset-bottom)); }
}
@media (max-width: 839px) and (max-height: 640px) {
  .keypad-recents, #dial-sub, #dial-rec { display: none !important; }
  #dialpad .dial-display { min-height: 44px; }
  #dialpad .dial-key { height: 36px; }
  #dialpad .dial-key > span { font-size: 24px; }
  #dialpad .dial-key small { font-size: 8px; }
  #dialpad .dial-foot { padding: 6px 0 0; }
  #dialpad .dial-call { width: 48px; height: 48px; }
  .keypad-layout { padding-bottom: calc(86px + env(safe-area-inset-bottom)); }
}

/* Settings in the app's own shape (SettingsScreen.kt): a category's icon
   sits on a neutral circle in the muted ink, never the accent; a section
   head is the small mono label in the faint ink, with no rule under it;
   the chevrons are barely there. */
.cat-ic { width: 36px; height: 36px; background: var(--glass-2); }
.cat-ic svg { width: 20px; height: 20px; fill: var(--text-2); }
.settings-group .cat-ic { background: var(--glass-2); }
.cat-card .cat-ic { width: 44px; height: 44px; background: transparent; }
.cat-card .cat-body b { font-size: 15.5px; font-weight: 600; }
.cat-body > span { font-size: 12px; }
.cat-chev { fill: var(--faint); }
.group > h3, .subhead {
  font: 500 10px var(--mono); letter-spacing: 1.4px; text-transform: uppercase;
  color: var(--faint); margin: 0; padding: 14px 0 6px; border: 0;
}
.group > h3.scene-label { padding-left: 0; }

/* The story pane before a row is picked (calls, contacts, voicemail). */
.pane-empty {
  flex: 1; display: none; flex-direction: column; align-items: center;
  justify-content: center; gap: 8px; padding: 0 44px 60px; text-align: center;
}
.pane-empty svg { width: 52px; height: 52px; color: var(--text-2); opacity: .7; }
.pane-empty b { font-size: 17px; }
.pane-empty span { font-size: 13.5px; color: var(--text-2); line-height: 1.45; max-width: 360px; }

/* THE PAD AT REST.
   Until a row is picked, the pane beside the list had nothing in it but
   a sentence saying to pick one. The keypad stands there instead: the
   thing you are most likely to want from a list of calls you have
   already made. Picking a row replaces it with that call's story, and
   closing it puts the sentence back with the button to raise it again.
   The docked shape below is exactly this shape, so resting borrows it
   at every desktop width rather than inventing a second one. */
@media (min-width: 840px) {
  #app > .body { position: relative; }
  body.pad-rest #keypadpane {
    display: flex; position: absolute; top: 0; right: 0; bottom: 0; left: var(--list);
    width: auto; height: auto; max-height: none;
    border: 0; border-left: 1px solid var(--border);
    border-radius: 0; box-shadow: none; background: transparent; z-index: 1;
  }
  body.pad-rest .keypad-layout {
    flex: 1; justify-content: center; width: 100%; max-width: 440px;
    margin: 0 auto; padding: 0 28px 28px;
  }
  body.pad-rest .keypad-recents { max-height: 40%; }
  /* At rest it is not a thing that was OPENED — no door is lit for it,
     because nothing was asked for. It closes, though: the same button,
     and the pane goes back to saying what it is for. */
  body.pad-rest #cd-empty { display: none; }

}

/* Between a phone and a wide desktop (840–1279px) the story pane is too
   narrow to share with a floating pad: there the pad docks INTO the
   story pane instead, and picking a row or a conversation puts it away
   (showCallDetail, open). */
@media (min-width: 840px) and (max-width: 1279px) {
  body.pad-open #keypadpane {
    position: absolute; top: 0; right: 0; bottom: 0; left: var(--list);
    width: auto; max-height: none; border: 0; border-left: 1px solid var(--border);
    border-radius: 0; box-shadow: none;
  }
  body.pad-open .keypad-layout { flex: 1; justify-content: center; width: 100%; max-width: 440px; margin: 0 auto; padding: 0 28px 28px; }
  body.pad-open .keypad-recents { max-height: 40%; }
}
/* On a wide desktop the pad floats; in Messages it floats above the
   composer, which lives in the same corner. */
@media (min-width: 1280px) {
  body.pad-open:not(.tab-calls):not(.tab-detail):not(.tab-contacts):not(.tab-voicemail):not(.settings-open) #keypadpane { bottom: 104px; }
  /* The story pane's placeholder centres in the room the pad leaves. */
  body.pad-open #cd-empty { padding-right: 440px; }
}

/* ---- Nothing pops: the line menu, the sheets, questions in place ----
 *
 * --sheet, --star and --missed were redeclared here too, and light
 * --missed read #d03a3e — the OTHER value design/tokens.json names as
 * the one not to use: "Android's #D03A3E on the light surface #F6F7F9 is
 * 4.4966:1 — just under", which is why the shared value is #c92a2a at
 * 5.0910:1. Since a missed call's name IS text, and since the missed red
 * also carries the Recents badge, the missed-call dot and the Do Not
 * Disturb warning, this one failed 1.4.3 everywhere it appeared. The
 * palette is design.css's; these three come from there now. */
/* The line menu: the phone app's LineChip DropdownMenu — a dot for the
   registration, the line in mono, its status under it, a check on the
   current one. Positioned under whichever chip opened it. */
#line-menu { position: fixed; top: 60px; left: 12px; right: auto; min-width: 280px;
  max-width: min(380px, calc(100vw - 16px)); }
#line-menu .menu-head { display: flex; justify-content: space-between; padding: 8px 16px 2px;
  font: 600 10.5px var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--text-2); }
#line-menu .item.line-choice { display: flex; align-items: stretch; padding: 0; cursor: default; }
#line-menu .item.line-choice:hover { background: transparent; }
#line-menu .line-pick { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0;
  padding: 10px 8px 10px 16px; border: 0; background: transparent; color: var(--text);
  font: inherit; text-align: left; cursor: pointer; }
#line-menu .line-pick:hover:not(:disabled) { background: var(--hover); }
#line-menu .line-pick:disabled { cursor: default; }
#line-menu .line-pick:disabled .line-words b { opacity: .6; }
#line-menu .line-pick[aria-checked="true"]:disabled .line-words b { opacity: 1; }
#line-menu .line-dnd { flex: none; width: 40px; margin: 6px 8px 6px 0; border: 0; border-radius: 10px;
  background: transparent; color: var(--text-2); cursor: pointer; display: flex;
  align-items: center; justify-content: center; }
#line-menu .line-dnd svg { width: 18px; height: 18px; fill: currentColor; }
#line-menu .line-dnd:hover:not(:disabled) { background: var(--hover); }
#line-menu .line-dnd[aria-checked="true"] { color: var(--missed);
  background: color-mix(in srgb, var(--missed) 14%, transparent); }
#line-menu .line-dnd:disabled { opacity: .4; cursor: default; }
.ld[data-state="warn"] { background: var(--star); }
.ld[data-state="bad"] { background: var(--missed); }
.ld[data-state="off"] { background: var(--faint); }
#line-menu .ld { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--primary); }
#line-menu .line-words { display: flex; flex-direction: column; min-width: 0; flex: 1; }
#line-menu .line-words b { font: 600 13px var(--mono); letter-spacing: .03em; overflow-wrap: anywhere; }
#line-menu .line-words span { font-size: 12px; color: var(--text-2); overflow-wrap: anywhere; }
#line-menu .line-pick[aria-checked="true"] .line-words b { color: var(--primary); }
#line-menu .line-check { color: var(--primary); font-weight: 700; flex: none; }
#line-menu .menu-note { padding: 10px 16px 6px; font-size: 12.5px; color: var(--text-2); line-height: 1.4; }
#line-menu .line-settings { border-top: 1px solid var(--border); margin-top: 6px; padding-top: 12px;
  color: var(--primary); font-weight: 600; }
/* Sheets: the phone app's GlassSheet — bottom-anchored, 28px shoulders, a
   grip — on a phone the full width, on a desktop centred at the foot. */
dialog.sheet { width: 100%; max-width: 560px; margin: auto auto 0; border: 0;
  border-radius: 28px 28px 0 0; max-height: 92dvh; overflow: auto; background: var(--sheet); }
dialog.sheet .dlg { padding: 10px 24px max(26px, env(safe-area-inset-bottom)); }
dialog.sheet .sheet-grip { display: block; margin: 0 auto 14px; background: var(--faint); }
.sheet-head { display: flex; align-items: center; gap: 8px; }
.sheet-head h3 { flex: 1; margin: 0; font-size: 17px; font-weight: 700; }
dialog.sheet h3 { margin-bottom: 0; font-size: 17px; font-weight: 700; }
.ct-flags .ghostbtn { color: var(--text-2); }
#ct-fav[aria-pressed="true"] { color: var(--star); }
#ct-block[aria-pressed="true"] { color: var(--missed); }
.ct-number { font-size: 13px; color: var(--text-2); margin: 2px 0 10px; }
#ct-edit .field:first-of-type { margin-top: 14px; }
.sheet-words { font-size: 13px; color: var(--text-2); line-height: 1.5; margin: 6px 0 16px; }
.sheet-actions { display: flex; align-items: center; gap: 8px; margin-top: 14px; }
/* The thread's own dialogs, in the phone app's DialogShell shape. */
dialog:not(.sheet):not(.incall):not(#lightbox) { border-radius: 24px; }
/* A question where it arose: the words, the one button, Cancel. */
.ask-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 12px 14px;
  margin: 8px 0 12px; border-radius: 14px; background: var(--note-bg); color: var(--note-text); font-size: 13px; line-height: 1.4; }
.ask-row > span { flex: 1 1 220px; }
.ws-switch { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }

/* ---- Settings → Phone in the phone app's shape ---- */
.managed-line .line-radio { width: 20px; height: 20px; accent-color: var(--primary); flex: none; margin: 0; cursor: pointer; }
.managed-line .line-radio:disabled { cursor: default; opacity: .5; }
.managed-line .line-chev { color: var(--faint); font-size: 22px; line-height: 1; flex: none; padding: 0 4px; }
#lines-group > .scene-label, #carrier-group > .scene-label { padding-left: 0; }
.about-notes { white-space: pre-line; font-size: 13px; line-height: 1.5; color: var(--text-2); margin: 0 0 8px; }
/* The permissions card: in the header, so it takes room instead of
   covering anything; not on the keypad, which needs its whole height. */
header.bar:has(> .permcard) { flex-wrap: wrap; }
.permcard { flex: 1 1 100%; order: 9; margin: 6px 0 2px; padding: 12px 14px 8px; border-radius: 16px;
  background: var(--note-bg); color: var(--note-text); font-size: 13px; line-height: 1.4; }
.permcard-head b { display: block; font-size: 14px; margin-bottom: 2px; }
.permcard-head span { display: block; opacity: .85; }
.permcard-row { display: flex; align-items: center; gap: 10px; padding: 10px 0; margin-top: 8px; border-top: 1px solid rgba(0,0,0,.1); }
.permcard-words { flex: 1; min-width: 0; }
.permcard-words b { display: block; font-weight: 600; }
.permcard-words span { display: block; font-size: 12.5px; opacity: .85; margin-top: 2px; }
.permcard .btn.outline { border-color: currentColor; color: inherit; flex: none; }
.permcard-foot { display: flex; justify-content: flex-end; }
.permcard-foot .btn.text { color: inherit; }
body.tab-keypad .permcard, body.settings-open .permcard { display: none; }

/* ---------- listening, under option C ----------
   The words are the message, so the player is a pill and a hairline.
   The browser's own <audio> controls are gone from both the voicemail
   row and a call's recording: they cannot be themed, they ignored every
   Nocturne token, and they drew themselves differently in each browser,
   which made one app look like three. */
.listen { display: flex; align-items: center; gap: 12px; }
.listen audio { display: none; }
.listen-btn {
  flex: none; height: 30px; padding: 0 13px 0 10px;
  display: inline-flex; align-items: center; gap: 6px;
  border: 0; border-radius: 15px;
  background: var(--primary); color: var(--on-primary);
  font: 500 12.5px var(--ui); cursor: pointer;
}
.listen-btn:hover { filter: brightness(1.06); }
.listen-ico { display: grid; place-items: center; }
.listen-ico svg { width: 13px; height: 13px; fill: currentColor; }
/* The line only appears once there is something to show on it: an
   untouched message is a pill and nothing else. */
.listen-bar {
  flex: 1 1 auto; height: 3px; border-radius: 2px;
  background: var(--glass-2); overflow: hidden; opacity: 0;
  transition: opacity .15s ease;
}
.listen.going .listen-bar { opacity: 1; }
.listen-bar > div { width: 0; height: 100%; border-radius: 2px; background: var(--primary); }
@media (prefers-reduced-motion: reduce) { .listen-bar { transition: none; } }

/* A recorded call: the pill, then what the call was about, then the
   words behind a toggle. */
.cd-said { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
.cd-ask { align-self: flex-start; height: 30px; padding: 0 13px; font-size: 12.5px; }
.sum-grid { display: flex; flex-wrap: wrap; gap: 10px; }
.sum {
  flex: 1 1 220px; min-width: 0;
  padding: 11px 14px; border-radius: 10px; background: var(--glass);
}
.sum-label {
  font: 500 9.5px var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-2); margin-bottom: 7px;
}
.sum p { margin: 0; font-size: 13.5px; line-height: 1.65; overflow-wrap: anywhere; }
.sum[dir="rtl"] p { line-height: 1.85; }
.cd-more { align-self: flex-start; height: 26px; padding: 0 10px; font-size: 12px; }
.cd-words {
  margin: 0; font: 400 13.5px/1.75 var(--ui); color: var(--text-2);
  white-space: pre-wrap; overflow-wrap: anywhere;
  padding: 12px 15px; border-radius: 10px; background: var(--glass);
  border-inline-start: 2px solid var(--border);
  max-height: 340px; overflow-y: auto;
}
.cd-none { margin: 0; font-size: 13px; color: var(--text-2); }

/* Settings → Workspace: the admin's own back office (workspace.ts). */
.ws-lede { margin: 0 0 14px; font-size: 13px; color: var(--text-2); line-height: 1.5; }
.ws-lede b { color: var(--text); font-weight: 500; }
.ws-lede.bad { color: var(--danger); }
.ws-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.ws-url { display: block; font-size: 12px; word-break: break-all; user-select: all; color: var(--text); }
.ws-editor { padding: 8px 0 20px; }
.ws-editor .field { max-width: 480px; }
.bc-confirm { display: flex; flex-direction: column; gap: 6px; padding: 14px 16px; margin: 12px 0;
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface); }
.bc-confirm > span { color: var(--text-2); font-size: 13px; }
.bc-confirm .bc-sample { color: var(--text); white-space: pre-wrap; border-left: 2px solid var(--primary);
  padding-left: 10px; margin: 4px 0; font-size: 14px; }
.bc-missed { font-size: 13px; color: var(--text-2); padding: 4px 0 12px; font-family: var(--mono); }
.ws-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin: 14px 0; }
.ws-tile { padding: 10px 12px; border-radius: 12px; background: var(--glass); min-width: 0; }
.ws-tile .k { display: block; font: 500 10px var(--mono); text-transform: uppercase; letter-spacing: 1px; color: var(--text-2); }
.ws-tile .v { display: block; font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; margin-top: 2px; }
.ws-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.ws-table td { padding: 8px 0; border-bottom: 1px solid var(--border); vertical-align: top; }
.ws-table td.r { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.ws-total { display: flex; justify-content: flex-end; align-items: baseline; gap: 12px; padding: 10px 0; font-size: 13px; color: var(--text-2); }
.ws-total b { font-size: 18px; color: var(--text); }
.ws-key { display: block; margin-top: 6px; font: 13px var(--mono); color: var(--text); overflow-wrap: anywhere; user-select: all; }
.ws-checks { display: flex; flex-direction: column; gap: 10px; margin: 8px 0 12px; font-size: 14px; }
.ws-checks label { display: flex; align-items: center; gap: 8px; }
.ws-checks input { width: auto; margin: 0; }
.ws-row-editor { padding: 6px 0 18px; border-bottom: 1px solid var(--border); }
.ws-row-editor .field { max-width: 420px; }
.ws-row-editor .switchline { display: flex; align-items: center; gap: 12px; margin: 6px 0 14px; font-size: 14px; }
.ws-state { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 12px; background: var(--glass); color: var(--text-2); margin-left: 6px; }
.ws-state.on { background: var(--primary-soft); color: var(--primary); }
.ws-state.warn { background: var(--note-bg); color: var(--note-text); }

/* A three-way choice, drawn as one control: who a number is for. */
.seg { display: inline-flex; flex-wrap: wrap; gap: 2px; padding: 3px; margin: 6px 0 4px;
       border-radius: 12px; background: var(--glass); }
.seg button { font: 600 13px var(--ui); color: var(--text-2); background: transparent;
              border: 0; padding: 8px 14px; border-radius: 9px; cursor: pointer; }
.seg button[aria-checked="true"] { background: var(--surface-2); color: var(--text);
                                   box-shadow: 0 1px 0 var(--border); }
.seg button:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }

/* A teammate typing in this conversation: one quiet line above the
   composer, with three dots breathing beside the name. */
.typingline { font: 500 12px var(--ui); color: var(--text-2); padding: 4px 20px 2px;
              display: flex; align-items: center; gap: 8px; }
.typingline .dots { display: inline-flex; gap: 3px; }
.typingline .dots i { width: 4px; height: 4px; border-radius: 50%; background: var(--text-2);
                      animation: typedot 1.2s infinite ease-in-out; }
.typingline .dots i:nth-child(2) { animation-delay: .15s; }
.typingline .dots i:nth-child(3) { animation-delay: .3s; }
@keyframes typedot { 0%, 60%, 100% { opacity: .25; } 30% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .typingline .dots i { animation: none; opacity: .6; } }
/* A person named in a note (@Sam Cohen). */
.note .mention { font-weight: 600; color: var(--note-text); }
/* The note composer's people picker for @mentions. */
.mention-pick { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 0; }
.mention-pick button { font: 600 12.5px var(--ui); padding: 6px 11px; border-radius: 999px;
                       border: 0; background: var(--glass-2); color: var(--text); cursor: pointer; }
.mention-pick button:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }

/* The signature that will be added, above the message box. */
.sigline { display: flex; align-items: center; gap: 10px; padding: 2px 20px 4px;
           font: 500 12px var(--ui); color: var(--text-2); }
.sigline span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sigline.off span { text-decoration: line-through; opacity: .6; }
.sigline button { flex: none; border: 0; background: none; color: var(--primary);
                  font: 600 12px var(--ui); cursor: pointer; padding: 2px 0; }
/* The saved-reply dialog's notes. */
.canned-vars { font-size: 12px; color: var(--text-2); margin: 4px 0 8px; line-height: 1.6; }
.canned-vars code { font-family: var(--mono); font-size: 11.5px; background: var(--glass);
                    padding: 1px 5px; border-radius: 5px; }
.canned-mine { display: flex; align-items: center; gap: 8px; font-size: 13px; margin: 4px 0 8px; }
.picker .item .mine { display: inline-block; margin-left: 8px; font: 600 10.5px var(--ui);
                      letter-spacing: .5px; text-transform: uppercase; color: var(--text-2); }
