
  :root{
    /* The UA's own furniture — caret, scrollbars, autofill, the file picker — follows this.
       Without it Chrome paints form controls in its light theme inside a near-black page. */
    color-scheme:dark;
    --ink:#03080a; --cloud:#fcfcfe;
    --accent:#4FC4B2; --accent-deep:#46AD9D; --accent-ink:#2C6159;
    --dbg:hsl(224 7% 4%);
    /* The reply surface. Bubble copy sits on this, so contrast is measured against it. */
    --dpanel:hsl(240 8% 6.6%);
    --dtext:#EAF1F4;              /* reply copy            16.1:1 on --dpanel */
    --dsub:rgba(234,242,245,.82); /* secondary sentences    8.9:1 */
    --dlabel:rgba(234,242,245,.62);/* mono labels, stamps    5.6:1 */
    --dline:hsla(198 22% 78% / .14);
    --dline-soft:hsla(198 22% 78% / .085);
    --dsurf:hsla(196 34% 72% / .055);
    --dsurf-hi:hsla(196 34% 72% / .09);
    --glow:rgba(79,196,178,.13);
    --mono:'Geist Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
    --sans:'Plus Jakarta Sans',system-ui,-apple-system,'Segoe UI',sans-serif;
    --r:8px; --r-card:14px; --r-bub:16px;
    /* ---------- the conversation surface: a message app, not a thread of documents ----------
       iMessage's dark appearance, because that is the shape a person already knows how to read
       at a glance: your own words on the right with a tail corner in the brand's seafoam, the
       agent's on the left in near-black grey, consecutive messages from one side pressed
       together with the name and clock printed once for the group.
       The outgoing bubble IS the site accent (#4FC4B2) — the same seafoam as the mark, the
       controls and the focus ring — so "mine" reads in the owner's own colour instead of a
       borrowed system blue. Bright seafoam cannot carry white text: white on it measures
       2.1:1. The ink is therefore dark teal (7.8:1 on the seafoam), and swapping these two
       tokens is the whole of changing the outgoing colour. */
    --imsg-out:var(--accent); --imsg-out-ink:#06231F;
    --imsg-in:#26262B; --imsg-in-ink:#F2F2F7; --r-imsg:19px;
    /* The brand mark as a backdrop. This is NOT an SVG <text> any more: an SVG loaded as an
       image cannot fetch a webfont, so the browser drew the letter in its own fallback face
       while the dot stayed pinned at coordinates written for Plus Jakarta's metrics — the
       dot landed on the stem (measured: 15-27px of overlap in DejaVu). These are the real
       Plus Jakarta Sans 800 outlines, dot included, baked into path data by tools/mark.py. */
    --wmark:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%20869%20769%27%3E%3Cg%20transform%3D%27translate%28-33.0%20757.0%29%20scale%281%20-1%29%27%3E%3Cpath%20d%3D%27M312.0%20-12Q232.0%20-12%20169.0%2026.0Q106.0%2064%2069.5%20129.0Q33.0%20194%2033.0%20273Q33.0%20353%2070.0%20417.5Q107.0%20482%20170.0%20520.0Q233.0%20558%20311.0%20558Q371.0%20558%20417.5%20535.5Q464.0%20513%20491.0%20472L468.0%20442V757H618.0V0H478.0V100L492.0%2069Q464.0%2029%20416.0%208.5Q368.0%20-12%20312.0%20-12ZM330.0%20123Q371.0%20123%20402.0%20142.0Q433.0%20161%20450.5%20195.0Q468.0%20229%20468.0%20273Q468.0%20317%20450.5%20351.0Q433.0%20385%20402.0%20404.0Q371.0%20423%20330.0%20423Q289.0%20423%20256.5%20403.5Q224.0%20384%20206.0%20350.5Q188.0%20317%20188.0%20273Q188.0%20229%20206.0%20195.0Q224.0%20161%20256.5%20142.0Q289.0%20123%20330.0%20123Z%27%20fill%3D%27%23fcfcfe%27%20fill-opacity%3D%27.085%27%2F%3E%3Cpath%20d%3D%27M752.0%200V150H902.0V0Z%27%20fill%3D%27%234FC4B2%27%20fill-opacity%3D%27.34%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  }
  *{box-sizing:border-box}
  html{height:100%}
  body{
    margin:0; height:100%; background:var(--ink); color:var(--dtext);
    font-family:var(--sans); font-weight:450; font-size:16px; line-height:1.6;
    -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
    -webkit-tap-highlight-color:transparent;
    overflow:hidden;
  }
  .mono{font-family:var(--mono);font-weight:400}
  :focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:6px}
  ::selection{background:rgba(79,196,178,.28)}
  .sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
  *{scrollbar-width:thin;scrollbar-color:hsla(198 22% 78% / .22) transparent}
  *::-webkit-scrollbar{width:10px;height:10px}
  *::-webkit-scrollbar-thumb{background:hsla(198 22% 78% / .18);border-radius:99px;border:3px solid transparent;background-clip:content-box}
  *::-webkit-scrollbar-thumb:hover{background:hsla(198 22% 78% / .3);background-clip:content-box}
  *::-webkit-scrollbar-track{background:transparent}

  /* ---------- backdrop: the brand, very quiet ---------- */
  .backdrop{
    position:fixed; inset:0; z-index:0; pointer-events:none;
    background-image:
      radial-gradient(115% 80% at 82% -16%, var(--glow), rgba(79,196,178,0) 64%),
      radial-gradient(90% 65% at 0% 106%, rgba(79,196,178,.055), rgba(79,196,178,0) 62%);
    background-repeat:no-repeat;
  }
  /* The mark itself is its own layer. Owner direction 2026-09-17: the lockup stands in the
     middle of the window — the letter in cloud white, the period in accent seafoam — instead
     of a seafoam silhouette cropped into the corner. Centred, nothing has to be faded out to
     keep it from competing with the conversation, so there is no mask any more. */
  .backdrop::after{
    content:""; position:absolute; inset:0;
    background:var(--wmark) no-repeat center center;
    background-size:min(40vw,420px) auto;
    opacity:.9;
  }
  @media (max-width:760px){
    .backdrop::after{
      background-size:min(62vw,270px) auto;
      opacity:.7;
    }
  }

  /* ---------- app frame ----------
     One identity block, two layouts: a top bar on a phone, a rail on a desktop.
     No duplicated ids, and the phone layout is the one a shop owner actually uses. */
  .app{
    position:relative; z-index:1; height:100dvh;
    display:grid; grid-template-columns:1fr;
    grid-template-rows:auto 1fr auto;
    grid-template-areas:"side" "thread" "composer";
  }

  /* ---------- the mark: lowercase d, accent dot ---------- */
  .mark{
    display:inline-flex; align-items:baseline; flex:0 0 auto;
    font-weight:800; font-size:26px; line-height:1; letter-spacing:-.05em; color:var(--cloud);
  }
  .mark i{font-style:normal;color:var(--accent);text-shadow:0 0 14px rgba(79,196,178,.55)}
  .mark.sm{font-size:22px}

  .side{
    grid-area:side; display:flex; align-items:center; gap:12px;
    padding:12px 16px; padding-top:max(12px,env(safe-area-inset-top));
    border-bottom:1px solid var(--dline);
    background:rgba(3,8,10,.74); backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
  }
  .brand{display:flex;align-items:center;gap:11px;flex:0 0 auto}
  .brandtext{display:none}
  .brandtext b em{font-style:normal;color:var(--accent)}

  .identity{min-width:0;flex:1 1 auto}
  .biz{
    display:block; font-weight:700; font-size:15px; letter-spacing:-.015em; color:var(--cloud);
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
  /* Narrow on purpose: a bare `.agent` rule here also matches every agent message
     turn (`class="turn agent"`) and rendered replies as tiny faint labels. */
  .agent-name{
    display:block; font-family:var(--mono); font-size:10.5px; letter-spacing:.12em;
    text-transform:uppercase; color:var(--dlabel); margin-top:4px;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }

  .status{
    flex:0 0 auto; display:flex; align-items:center; gap:7px;
    padding:6px 11px 6px 10px; border:1px solid var(--dline); border-radius:999px; background:var(--dsurf);
    font-family:var(--mono); font-size:10px; letter-spacing:.12em; text-transform:uppercase; color:var(--dlabel);
  }
  .identity .status{padding:2px 0;border:0;background:transparent;letter-spacing:.08em}
  .dot{width:7px;height:7px;border-radius:50%;background:hsl(0 0% 42%);flex:0 0 auto}
  .dot.up{background:var(--accent)}
  .dot.down{background:hsl(38 60% 56%)}

  .sidefoot{display:none}
  .railhead{display:none}

  /* ---------- thread ---------- */
  main.thread{
    grid-area:thread; min-height:0; overflow-y:auto; overscroll-behavior:contain;
    padding:22px 16px 12px; scroll-padding-bottom:16px;
  }
  .wrap{max-width:768px;width:100%;margin:0 auto;display:flex;flex-direction:column;gap:22px}

  .turn{display:flex;flex-direction:column;max-width:84%;gap:5px}
  .turn.me{align-self:flex-end;align-items:flex-end}
  /* stretch, not flex-start: with flex-start the reply column takes its content width, so a
     single long unbreakable line (a SQL block) dragged the bubble 600px wide on a 390px
     phone instead of scrolling inside its own box. */
  .turn.agent{align-self:flex-start;align-items:stretch;width:100%;max-width:100%}

  /* ---------- grouping ----------
     Consecutive messages from ONE side read as one block: 4px instead of 22px, and the name
     and the clock printed once for the group rather than once per line. This is most of what
     makes a message app scannable, and it is why the wrap gap is 22px and reduced here — a
     day line between two turns breaks the grouping, which is exactly right. */
  .wrap > .turn.me + .turn.me,
  .wrap > .turn.agent + .turn.agent{margin-top:-18px}
  .wrap > .turn.me + .turn.me .meta,
  .wrap > .turn.agent + .turn.agent .meta{display:none}

  .meta{display:flex;align-items:center;gap:9px;flex-wrap:wrap;font-size:11px;color:var(--dlabel)}
  .turn.me .meta{flex-direction:row-reverse}
  .meta .who{
    font-family:var(--mono); font-size:10.5px; letter-spacing:.12em;
    text-transform:uppercase; color:var(--dlabel);
  }
  /* Your own messages carry no name: you know who you are, and the label is the thing that
     made both sides of this thread look alike at a glance. */
  .turn.me .who{display:none}
  .turn.agent .meta{padding-left:2px}
  .stamp{font-family:var(--mono);font-size:10.5px;color:var(--dlabel)}
  .stamp::before{content:"·";margin-right:9px;opacity:.55}
  .turn.me .stamp::before{content:"";margin:0}
  .copy{
    appearance:none; background:none; border:0; padding:0; cursor:pointer;
    font-family:var(--mono); font-size:10px; letter-spacing:.11em; text-transform:uppercase;
    color:var(--dlabel); opacity:0; transition:opacity .15s ease,color .15s ease;
  }
  .turn:hover .copy,.copy:focus-visible{opacity:1}
  @media (hover:none){.copy{opacity:.65}}
  .copy:hover{color:var(--accent)}

  .line{display:flex;gap:12px;align-items:flex-start;min-width:0}
  .turn.me .line{justify-content:flex-end}
  /* A one-to-one conversation has no avatars. The brand mark is in the rail, and a 26px square
     beside every reply is what made this read as a document viewer. */
  .avatar{display:none}
  .avatar i{font-style:normal;color:var(--accent)}

  /* ---------- the bubbles ---------- */
  .bubble{
    min-width:0; padding:9px 14px; border-radius:var(--r-imsg);
    overflow-wrap:anywhere; font-size:16px; line-height:1.68;
  }
  .turn.me .bubble{
    background:var(--imsg-out); color:var(--imsg-out-ink); font-weight:500;
    border-bottom-right-radius:6px; white-space:pre-wrap;
    /* Tight, because nothing long is ever typed here — and the prose leading the reply column
       is measured and gate-checked, so it is not the place to borrow iMessage's rhythm. */
    line-height:1.45;
  }
  /* The tail corner belongs to the LAST bubble of a group, and the first one gets its full
     radius at the top — that is the iMessage stacking, and it is what makes two messages from
     the same side look like one utterance. */
  .turn.me + .turn.me .bubble{border-bottom-right-radius:var(--r-imsg)}
  .turn.me:has(+ .turn.me) .bubble{border-top-right-radius:7px}
  /* The measure cap is the whole point: a 750-character paragraph in a 780px column is a
     wall. 33rem is ~73 characters of this type at 16px — measured, not guessed: the average
     prose character is 7.2px, so this is where the copy stops being tiring. */
  .turn.agent .bubble{
    background:var(--imsg-in); color:var(--imsg-in-ink);
    border-bottom-left-radius:6px;
    max-width:min(100%,33rem);
  }
  .turn.agent + .turn.agent .bubble{border-bottom-left-radius:var(--r-imsg)}
  .turn.agent:has(+ .turn.agent) .bubble{border-top-left-radius:7px}

  /* ---------- formatted replies ----------
     Agent output goes through a small safe renderer: text nodes only, never
     innerHTML. These rules are what make a long reply readable. */
  .bubble > *:first-child{margin-top:0}
  .bubble > *:last-child{margin-bottom:0}
  .bubble p{margin:0 0 13px; text-wrap:pretty}
  .bubble p:last-child{margin-bottom:0}
  .bubble .md-h1,.bubble .md-h2,.bubble .md-h3{
    margin:20px 0 9px; font-weight:750; letter-spacing:-.02em; line-height:1.3;
    color:var(--cloud); text-wrap:balance;
  }
  .bubble > *:first-child.md-h1,.bubble > *:first-child.md-h2,.bubble > *:first-child.md-h3{margin-top:0}
  .bubble .md-h1{font-size:18.5px}
  .bubble .md-h2{font-size:16.5px}
  .bubble .md-h2::after{content:"";display:block;width:26px;height:2px;border-radius:2px;margin-top:7px;background:rgba(79,196,178,.5)}
  .bubble .md-h3{font-size:15px;color:var(--dsub);text-transform:none}

  .bubble ul,.bubble ol{margin:0 0 13px; padding-left:0; list-style:none}
  .bubble ul ul,.bubble ul ol,.bubble ol ul,.bubble ol ol{margin:6px 0 0}
  .bubble li{position:relative;padding-left:20px;margin:0 0 7px}
  .bubble li:last-child{margin-bottom:0}
  .bubble li::before{content:"";position:absolute;left:4px;top:.62em;width:5px;height:5px;border-radius:50%;background:var(--accent);opacity:.85}
  .bubble ol{counter-reset:mdli}
  .bubble ol > li{counter-increment:mdli}
  .bubble ol > li::before{
    content:counter(mdli); top:.1em; left:0; width:auto; height:auto; border-radius:0;
    font-family:var(--mono); font-size:12px; color:var(--accent); opacity:1;
  }
  .bubble .md-check{position:absolute;left:0;top:.18em;width:13px;height:13px;border:1px solid var(--dline);border-radius:4px;
    display:flex;align-items:center;justify-content:center;font-size:10px;line-height:1;color:var(--ink);background:transparent}
  .bubble li.md-task::before{display:none}
  .bubble li.md-task.done .md-check{background:var(--accent);border-color:var(--accent)}

  .bubble blockquote{
    margin:0 0 13px; padding:9px 14px; border-radius:10px;
    border-left:2px solid rgba(79,196,178,.5); background:var(--dsurf);
    color:var(--dsub);
  }
  .bubble blockquote p{margin:0 0 8px}
  .bubble blockquote p:last-child{margin:0}
  .bubble hr{border:0;border-top:1px solid var(--dline);margin:18px 0}
  .bubble code{
    font-family:var(--mono); font-size:.86em; padding:2px 6px; border-radius:6px;
    background:hsla(196 34% 72% / .1); border:1px solid var(--dline-soft);
    overflow-wrap:anywhere; color:#F3F8F9;
  }
  .bubble pre{
    margin:0 0 13px; padding:12px 14px; max-width:100%; overflow-x:auto;
    border:1px solid var(--dline); border-radius:10px; background:rgba(3,8,10,.66);
  }
  .bubble pre code{padding:0;border:0;background:none;font-size:13px;line-height:1.65;white-space:pre;color:var(--dtext)}
  .bubble .md-code{margin:0 0 13px}
  .bubble .md-lang{
    font-family:var(--mono); font-size:9.5px; letter-spacing:.14em; text-transform:uppercase;
    color:var(--dlabel); margin:0 0 6px 2px;
  }
  .bubble .md-code pre{margin:0}
  /* Tables: the format a reply most often arrives in when the agent has done real work. */
  .bubble .md-table-wrap{
    margin:0 0 13px; max-width:100%; overflow-x:auto;
    border:1px solid var(--dline); border-radius:10px; background:rgba(3,8,10,.32);
  }
  .bubble table.md-table{border-collapse:collapse; width:100%; font-size:14.5px; line-height:1.5}
  .bubble table.md-table th{
    font-family:var(--mono); font-size:10px; font-weight:400; letter-spacing:.1em; text-transform:uppercase;
    color:var(--dlabel); text-align:left; padding:9px 13px; background:var(--dsurf);
    border-bottom:1px solid var(--dline); white-space:nowrap;
  }
  .bubble table.md-table td{
    padding:9px 13px; border-bottom:1px solid var(--dline-soft); vertical-align:top;
  }
  .bubble table.md-table tbody tr:last-child td{border-bottom:0}
  .bubble table.md-table tbody tr:nth-child(even) td{background:hsla(196 34% 72% / .022)}
  .bubble table.md-table .md-num{text-align:right;font-variant-numeric:tabular-nums}
  .bubble table.md-table .md-center{text-align:center}
  .bubble strong{font-weight:700;color:var(--cloud)}
  .bubble em{font-style:italic}
  .bubble a{color:var(--accent);text-underline-offset:3px;text-decoration-thickness:1px}
  .bubble a:hover{color:#66D3C2}
  .turn.me .bubble a{color:var(--imsg-out-ink);text-decoration:underline;text-underline-offset:2px}
  .turn.me .bubble code{background:rgba(3,8,10,.14);border-color:rgba(3,8,10,.18)}
  .bubble s{opacity:.7}

  /* ---------- working ---------- */
  .progress{display:flex;flex-direction:column;gap:7px;padding-left:2px}
  .progress p{
    margin:0; font-family:var(--mono); font-size:11px; letter-spacing:.01em; color:var(--dlabel);
    display:flex; align-items:baseline; gap:8px;
  }
  .progress p::before{content:"";width:4px;height:4px;border-radius:50%;background:var(--accent);flex:0 0 auto}
  /* The dots that mean "the agent is writing RIGHT NOW", plus the one short line that says
     what it is doing while the wait runs long.

     Muted and small on purpose: an accent-coloured dot pulsing in a just-finished reply is what
     made the owner read "still loading" off a message that had already landed. Nothing here glows.

     ONE ROW PER THREAD, never two. A run can be represented twice for a moment — the events
     channel can name it before this page's own /api/chat stream does, and each of those created
     its own dot group — so two sets of dots sat on screen through the longest part of the wait: the
     owner's report, "there's 2 sets of 3 dots". `onlyThinking()` keeps the newest and takes the
     rest off; the page's double-representation clean-up later finishes the job. */
  .pending{display:flex;gap:9px;align-items:baseline;padding:6px 0 0 2px;min-height:15px}
  .pending .dots{display:inline-flex;gap:5px;align-items:center;flex:0 0 auto}
  .pending i{width:4px;height:4px;border-radius:50%;background:var(--dsub);animation:pulse 1.6s ease-in-out infinite}
  .pending i:nth-child(2){animation-delay:.2s}
  .pending i:nth-child(3){animation-delay:.4s}
  /* The line is a thought, not a log: sentence case, no monospace machinery, and the wording
     every customer already uses for someone working on their problem. */
  .pending .says{font-size:12px;color:var(--dlabel);letter-spacing:.005em;transition:opacity .22s ease;flex:1 1 auto;min-width:0}
  .pending .says.swap{opacity:0}
  @keyframes pulse{0%,100%{opacity:.2}50%{opacity:1}}

  /* ---------- decisions ----------
     The Discord buttons, for a browser. A card, not a command: the flagged command is shown
     verbatim (the agent redacts credentials before it ever reaches this page), what it means
     is said in plain words, and the choices are the AGENT's own list — the portal renders
     exactly what the agent will accept, so a button can never be one it would refuse.
     It sits inside the turn that caused it, because that is what a decision is part of. */
  .decide{
    margin:11px 0 0 38px; padding:14px 15px 13px; max-width:560px;
    border:1px solid var(--dline); border-left:2px solid var(--accent);
    border-radius:12px; background:linear-gradient(180deg,var(--dsurf-hi),var(--dsurf));
  }
  .decide-kicker{
    margin:0 0 9px; font-family:var(--mono); font-size:10px; letter-spacing:.14em;
    text-transform:uppercase; color:var(--accent); display:flex; align-items:center; gap:8px;
  }
  .decide-kicker i{width:6px;height:6px;border-radius:50%;background:var(--accent);animation:pulse 1.25s ease-in-out infinite;flex:0 0 auto}
  .decide-why{margin:0 0 10px; font-size:14.5px; line-height:1.55; color:var(--dsub)}
  .decide-why b{color:var(--dtext);font-weight:600}
  .decide pre{
    margin:0 0 12px; padding:11px 13px; border:1px solid var(--dline); border-radius:10px;
    background:rgba(3,8,10,.6); overflow-x:auto;
  }
  .decide pre code{
    font-family:var(--mono); font-size:12.5px; line-height:1.6; color:var(--dtext);
    white-space:pre-wrap; overflow-wrap:anywhere;
  }
  .decide-actions{display:flex;flex-wrap:wrap;gap:8px}
  .decide-btn{
    font-family:var(--sans); font-size:14px; font-weight:600; letter-spacing:.01em;
    padding:10px 14px; min-height:42px; border-radius:9px; cursor:pointer;
    border:1px solid var(--dline); background:var(--dsurf-hi); color:var(--dtext);
    transition:background .14s ease,border-color .14s ease,transform .1s ease,opacity .14s ease;
  }
  .decide-btn:hover{background:hsla(196 34% 72% / .16)}
  .decide-btn:active{transform:translateY(1px)}
  .decide-btn.allow{border-color:rgba(79,196,178,.5);color:#BDF0E6}
  .decide-btn.allow:hover{background:rgba(79,196,178,.18)}
  .decide-btn.deny{border-color:rgba(226,120,110,.42);color:#F4CFC9}
  .decide-btn.deny:hover{background:rgba(226,120,110,.16)}
  .decide-btn[disabled]{opacity:.42;cursor:default;transform:none}
  .decide-btn[data-picked="1"]{border-color:var(--accent);background:rgba(79,196,178,.2);opacity:1}
  .decide-foot{margin:10px 0 0; font-family:var(--mono); font-size:10.5px; letter-spacing:.03em; line-height:1.5; color:var(--dlabel)}
  /* Answered or lapsed: the card keeps its place in the timeline and stops asking. */
  .decide[data-state="done"],.decide[data-state="lapsed"]{background:var(--dsurf)}
  .decide[data-state="done"] .decide-kicker,.decide[data-state="lapsed"] .decide-kicker{color:var(--dlabel)}
  .decide[data-state="done"] .decide-kicker i{animation:none;opacity:.5;background:var(--dlabel)}
  .decide[data-state="lapsed"]{border-left-color:rgba(226,120,110,.5)}
  .decide[data-state="lapsed"] .decide-kicker i{animation:none;opacity:.6;background:#E2786E}
  @media (max-width:560px){ .decide{margin-left:0} .decide-btn{flex:1 1 100%} }

  .note{
    align-self:center; max-width:520px; text-align:center; font-size:13px; color:var(--dsub);
    padding:10px 15px; border:1px solid var(--dline); border-radius:12px; background:var(--dsurf);
  }
  .dateline{
    align-self:center; display:flex; align-items:center; gap:10px; width:100%; max-width:520px;
    font-family:var(--mono); font-size:10px; letter-spacing:.14em; text-transform:uppercase; color:var(--dlabel);
  }
  .dateline::before,.dateline::after{content:"";height:1px;background:var(--dline-soft);flex:1 1 auto}

  /* "Show earlier": the top of a thread that has grown past a dozen questions, folded away.
     It sits IN the scroller rather than pinned like button.jump — the reader who wants it is at
     the very top of the conversation, where it scrolls off as they read on, instead of hanging
     over the history they came back to read. */
  .earlier{align-self:center}
  .earlier button{
    min-height:34px; padding:5px 14px; cursor:pointer;
    border:1px solid var(--dline); border-radius:999px; background:var(--dsurf-hi); color:var(--dtext);
    font-family:var(--mono); font-size:10px; letter-spacing:.12em; text-transform:uppercase;
  }
  .earlier button:hover{background:hsla(196 34% 72% / .16)}

  /* The seam: a zero-height node between revealed history and what was already on screen.
     The grouping rules below use ADJACENCY (`+`) to decide that a message continues the one
     before it — so without a node in between, a reply revealed from history and the reply that
     was already drawn merge into one utterance: 4px apart, name and clock suppressed. Its
     negative margins eat the wrap's own 22px gap on both sides, so the seam is invisible. */
  .seam{height:0;margin-top:-11px;margin-bottom:-11px}

  /* The chats bar that used to sit here (sticky at the top of the thread: "New chat" and the way
     back through the old ones) is gone — owner direction 2026-09-18, "the feature isn't needed any
     more". Chats are no longer cut by hand, so there is nothing to start and nothing to step
     between; the live thread is the one conversation, folded to the window the server decides.
     What remains of archives is READ-only and URL-driven (`?chat=`), which is still why the
     reading-old rules below exist. */
  /* Reading an earlier chat: the composer goes, and the thread takes its row — a box that cannot
     send anything is a lie about what this screen does. */
  body.reading-old form.composer{display:none}
  body.reading-old .app{grid-template-rows:auto 1fr;grid-template-areas:"side" "thread"}
  @media (min-width:1000px){body.reading-old .app{grid-template-areas:"side thread"}}

  .empty{margin:auto;text-align:center;max-width:420px;padding:22px;display:flex;flex-direction:column;align-items:center;gap:12px}
  .empty h1{font-size:19px;font-weight:750;letter-spacing:-.025em;margin:0;color:var(--cloud)}
  .empty p{margin:0;color:var(--dsub);font-size:15px}
  /* States the console did not have. A read that failed used to be drawn as an empty inbox
     ("You're all caught up.", "Nothing new."), which is a claim about the client's work made on
     the strength of a request that never landed. The retry is the way out of it. */
  .waiting-nothing{margin:0 0 14px;color:var(--dsub);font-size:13px}
  .waiting-error{margin:8px 0 0;color:rgba(255,154,154,.95);font-size:12px;line-height:1.45}
  .alerts-error{margin:0;color:var(--dsub);font-size:14px}
  .waiting-retry,.alerts-retry{display:block;margin:16px auto 0;min-height:34px;padding:0 14px;border:1px solid var(--dline);border-radius:8px;background:transparent;color:var(--dsub);font:10px var(--mono);cursor:pointer}
  .waiting-retry:hover,.alerts-retry:hover{border-color:var(--accent);color:var(--cloud)}
  /* The notification's title is the control that opens it. It inherits the h3's type so the row
     reads exactly as it did, and the row itself is no longer a button with a button inside it. */
  .alert-open{width:100%;padding:0;border:0;background:none;color:inherit;font:inherit;text-align:left;cursor:pointer}
  .alert-open:hover{color:var(--cloud)}

  /* ---------- composer ---------- */
  form.composer{
    /* The positioning context for the jump bubble, which hangs above this panel (see button.jump).
       Relative with no z-index: the composer still paints where it did, under the sticky chats bar. */
    position:relative;
    grid-area:composer; display:flex; flex-direction:column; gap:8px;
    padding:13px 16px; padding-bottom:max(13px,env(safe-area-inset-bottom));
    border-top:1px solid var(--dline);
    background:rgba(3,8,10,.74); backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
  }
  /* 5px on all four sides: the field's inset used to be 15px left / 5px right, which was
     right for a bare text field and wrong once the + was inserted at the head of the row —
     it pushed the + 10px further in than Send and read as a control that had drifted. */
  .field{
    width:100%; max-width:768px; margin:0 auto;
    display:flex; align-items:flex-end; gap:8px;
    padding:5px; border:1px solid var(--dline); border-radius:15px; background:var(--dsurf);
    transition:border-color .16s ease, box-shadow .16s ease, background .16s ease;
  }
  .field:focus-within{border-color:rgba(79,196,178,.45);box-shadow:0 0 0 3px rgba(79,196,178,.09);background:var(--dsurf-hi)}
  textarea{
    /* A pixel floor, not 0. `min-width:0` lets the field shrink to nothing, but it also
       means the row's intrinsic width is the placeholder's full width — and that became the
       grid track's min-content, pushing the whole page 8px wider than a 390px phone once the
       attach control joined the row. A floor keeps the row shrinkable and the page inside
       the viewport. */
    flex:1 1 auto; min-width:76px; resize:none; min-height:44px; max-height:132px;
    padding:10px 0; border:0; background:transparent; color:var(--dtext);
    font-family:var(--sans); font-size:16px; line-height:1.45;
  }
  textarea:focus{outline:none}
  textarea::placeholder{color:var(--dlabel)}
  /* 10px, not 11px: the field is 15px with a 5px inset, and concentric radii are what make an
     inset control look seated rather than stuck on. The highlight is the small thing that
     stops a flat accent rectangle reading as a placeholder. */
  button.send{
    flex:0 0 auto; min-width:84px; min-height:46px; padding:0 18px; cursor:pointer;
    border:1px solid transparent; border-radius:10px; background:var(--accent); color:var(--ink);
    font-family:var(--sans); font-weight:650; font-size:14.5px; letter-spacing:-.005em;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.18), 0 8px 20px -14px rgba(79,196,178,.75);
    transition:background .16s ease,transform .16s ease,box-shadow .16s ease;
  }
  button.send:hover{background:var(--accent-deep);box-shadow:inset 0 1px 0 rgba(255,255,255,.22), 0 10px 22px -14px rgba(79,196,178,.9)}
  button.send:active{transform:translateY(1px);box-shadow:inset 0 1px 0 rgba(255,255,255,.14)}
  button.send[disabled]{opacity:.5;cursor:default;transform:none}
  .hintrow{
    display:none; width:100%; max-width:768px; margin:0 auto; text-align:center;
    font-family:var(--mono); font-size:10px; letter-spacing:.06em; color:var(--dlabel); opacity:.8;
  }

  /* The "there is more below" bubble. It FLOATS above the composer rather than sitting in it:
     as the composer's first row, the panel's own blurred surface and its top border grew up to
     wrap the control, so the bubble read as part of the composer instead of a thing belonging to
     the thread above it (owner report 2026-09-18: "the background goes up to it").
     Out of flow it also stops resizing the composer when it appears, which retires the slack
     `atBottom()` carried for the pill's own height.
     It hangs over the composer's top edge, right-aligned to the MESSAGE COLUMN rather than to the
     window — `max(16px, (100% - 768px)/2)` is the field's own right edge under any symmetric
     composer padding, so the bubble lines up with Send at every width. Solid surface (--dpanel,
     not a translucent wash): it floats over message text and must not let it read through. */
  button.jump{
    position:absolute; z-index:5; bottom:calc(100% + 12px);
    right:max(16px, calc((100% - 768px) / 2));
    display:inline-flex; align-items:center; gap:8px;
    min-height:38px; padding:0 15px 0 13px; cursor:pointer;
    border:1px solid var(--dline); border-radius:999px;
    background:var(--dpanel); color:var(--dtext);
    font-family:var(--mono); font-size:10px; letter-spacing:.12em; text-transform:uppercase;
    box-shadow:0 14px 30px -16px rgba(0,0,0,.95), 0 2px 8px rgba(0,0,0,.4),
               inset 0 1px 0 hsla(198 22% 78% / .07);
    transition:background .16s ease, border-color .16s ease, color .16s ease, transform .16s ease,
               box-shadow .16s ease;
  }
  /* The way BACK DOWN is a downward arrow: it says which way the thread goes before the label
     is read, which is the whole of what a floating scroll control has to do. */
  button.jump svg{width:15px;height:15px;flex:0 0 auto;opacity:.9}
  button.jump:hover{background:hsl(240 8% 11%);border-color:hsla(198 22% 78% / .3)}
  button.jump:active{transform:translateY(1px)}
  /* Something actually arrived while you were reading back: the bubble wears the accent, so the
     state is visible at a glance and there is no badge to decode. */
  button.jump[data-fresh="1"]{
    border-color:rgba(79,196,178,.46); color:var(--accent);
    box-shadow:0 14px 30px -16px rgba(0,0,0,.95), 0 10px 26px -18px rgba(79,196,178,.9),
               inset 0 1px 0 hsla(196 34% 72% / .12);
  }
  button.jump[data-fresh="1"]:hover{background:hsl(196 24% 12%);border-color:rgba(79,196,178,.62)}
  button.jump[hidden]{display:none}

  /* A turn being written on ANOTHER device right now (see the live channel). It is drawn like
     any other agent turn so it does not read as a different kind of thing, with the working
     dots still on it and the last thing the agent did underneath. */
  .turn.live .progress{margin-top:0}
  .turn.live .bubble{opacity:.94}
  .live-stop{
    margin:7px 0 0 38px; font-family:var(--mono); font-size:10.5px; letter-spacing:.04em;
    color:var(--dlabel);
  }
  @media (max-width:560px){ .live-stop{margin-left:0} }

  /* A comment sent while the agent is still writing (see comment() below). It sits under the
     reader's own bubble, in the reader's own column, and says what happened to it: added to
     the answer being written, or held until that answer is done. Small and quiet — it is a
     receipt for the sender's own words, not a message of its own. */
  .atnote{
    margin:7px 2px 0 auto; font-family:var(--mono); font-size:10.5px; letter-spacing:.04em;
    color:var(--dlabel); text-align:right; max-width:min(76%, 420px);
  }
  @media (max-width:560px){ .atnote{max-width:88%} }

  @media (prefers-reduced-motion:reduce){
    .pending i{animation:none;opacity:.6}
    button.send:active,button.attach:active{transform:none}
  }

  /* Grid items default to `min-width:auto`, which means the widest min-content inside a
     track becomes the track's floor — the composer row was 398px of min-content, so the
     whole page was 8px wider than a 390px phone. These three are the grid areas; letting
     them shrink is what keeps every screen inside the viewport. */
  .app > aside.side,.app > main.thread,.app > form.composer{min-width:0}

  /* ---------- documents ----------
     A document is a turn in the same timeline as the conversation, because that is how a
     client thinks about it: "I sent you the quote on Tuesday" sits between what they said
     on Monday and what the agent said on Wednesday. The card carries the name, the size,
     who it came from and one download button — nothing else, because the alternative is a
     file manager nobody asked for. */
  .fcard{
    display:flex; align-items:center; gap:11px; min-width:0;
    padding:11px 12px; border:1px solid var(--dline); border-radius:13px;
    background:var(--dsurf); max-width:min(100%,26rem);
  }
  .ficon{
    flex:0 0 auto; width:36px; height:40px; border-radius:8px;
    display:flex; align-items:center; justify-content:center;
    border:1px solid var(--dline); background:hsla(196 34% 72% / .10); color:var(--dsub);
    font-family:var(--mono); font-size:9px; letter-spacing:.04em; text-transform:uppercase;
  }
  .fmeta{min-width:0; flex:1 1 auto; display:flex; flex-direction:column; gap:2px}
  .fmeta b{font-size:14.5px; font-weight:600; color:var(--dtext); overflow-wrap:anywhere}
  .fmeta small{font-family:var(--mono); font-size:9.5px; letter-spacing:.05em; color:var(--dlabel)}
  .fnote{font-size:13px; line-height:1.45; color:var(--dsub); overflow-wrap:anywhere}
  a.fdl{
    flex:0 0 auto; text-decoration:none; padding:9px 11px; border-radius:9px;
    border:1px solid var(--dline); color:var(--accent);
    font-family:var(--mono); font-size:10px; letter-spacing:.08em; text-transform:uppercase;
  }
  a.fdl:hover{border-color:rgba(79,196,178,.45); background:hsla(196 34% 72% / .07)}

  /* The dock is above the box, so an upload is visible while it happens — a card that
     appears with no explanation is how "did it send?" turns into a support question. */
  .dock{width:100%; max-width:768px; margin:0 auto; display:flex; flex-direction:column; gap:6px}
  /* `hidden` has to win. The UA's [hidden]{display:none} loses to the author `display:flex`, so a
     hidden dock stayed a 0-height item in the composer's column and its 8px gap pushed the field
     down — measured: 21px above the field against 13px below, which is the composer looking
     top-heavy for no visible reason. */
  .dock[hidden]{display:none}
  .chip{
    display:flex; align-items:center; gap:9px; min-width:0;
    padding:8px 11px; border:1px solid var(--dline); border-radius:10px; background:var(--dsurf);
    font-size:13.5px; color:var(--dsub);
  }
  .chip b{min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--dtext); font-weight:600}
  .chip small{flex:0 0 auto; max-width:58%; text-align:right; font-family:var(--mono); font-size:9.5px; letter-spacing:.04em; color:var(--dlabel)}
  .chip .bar{flex:1 1 auto; height:3px; border-radius:2px; background:var(--dline); overflow:hidden}
  .chip .bar i{display:block; height:100%; width:0; background:var(--accent); transition:width .2s ease}
  .chip.bad{border-color:rgba(255,140,140,.45)}
  .chip.bad small{color:#ffb3b3}
  /* A staged attachment is NOT on its way yet: it waits for Send, so the text in the box can
     become its caption — and it can be taken back. The × is a real button, because on a phone
     there is no "just clear the field". */
  .chip.staged{border-style:solid}
  .chip.staged b{flex:1 1 auto}
  .chip .x{
    flex:0 0 auto; width:26px; height:26px; padding:0; cursor:pointer;
    display:flex; align-items:center; justify-content:center;
    border:0; border-radius:7px; background:transparent; color:var(--dsub);
  }
  .chip .x:hover{background:var(--dline); color:var(--dtext)}
  .chip .x svg{display:block; width:11px; height:11px}
  /* The caption is carried by the file, so the thread shows it under the file's card rather
     than as a second bubble. Same voice, one turn. */
  .fnote{font-style:normal}
  /* The cross is a 24-unit drawing in a flex-centred box, not a "+" character: a text glyph
     is positioned by its font's baseline and math axis, and at 20px/1 that put the ink 3px
     below the button's centre (measured from a 4x screenshot). Geometry cannot drift. */
  button.attach,button.mic{
    flex:0 0 auto; width:46px; min-height:46px; padding:0; cursor:pointer;
    display:flex; align-items:center; justify-content:center;
    border:1px solid var(--dline); border-radius:10px; background:transparent;
    color:var(--dsub);
    transition:border-color .16s ease, color .16s ease, background .16s ease, transform .16s ease;
  }
  button.attach svg,button.mic svg{display:block;width:19px;height:19px}
  button.attach:hover,button.mic:hover{border-color:rgba(79,196,178,.45); color:var(--accent); background:var(--dsurf)}
  button.attach:active,button.mic:active{transform:translateY(1px)}
  button.attach[disabled],button.mic[disabled]{opacity:.45;cursor:default;transform:none}
  button.mic[aria-pressed="true"]{width:76px;border-color:rgba(255,140,140,.65);background:rgba(255,100,100,.12);color:#ffb3b3}
  button.mic .voice-timer{display:none;margin-left:4px;font:10px var(--mono);letter-spacing:.02em}
  button.mic[aria-pressed="true"] .voice-timer{display:inline}
  .voice-cancel{align-self:flex-end;border:1px solid var(--dline);border-radius:8px;background:transparent;color:var(--dsub);padding:6px 10px;cursor:pointer;font:11px var(--mono)}
  .voice-cancel:hover{border-color:rgba(255,140,140,.55);color:#ffb3b3}
  ul.docs{display:none}
  .docs-all{display:none}

  /* ---------- desktop: the rail ---------- */
  @media (min-width:1000px){
    .app{
      grid-template-columns:264px 1fr; grid-template-rows:1fr auto;
      grid-template-areas:"side thread" "side composer";
    }
    .side{
      flex-direction:column; align-items:stretch; gap:16px;
      padding:24px 22px; border-bottom:0; border-right:1px solid var(--dline);
      background:linear-gradient(180deg, hsla(196 34% 72% / .045), rgba(3,8,10,0) 46%);
    }
    .brand{gap:12px}
    .mark{font-size:30px}
    .brandtext{display:flex;flex-direction:column;gap:4px;line-height:1.1}
    .brandtext b{font-size:16px;font-weight:800;letter-spacing:-.03em;color:var(--cloud)}
    .brandtext small{
      font-family:var(--mono);font-size:9.5px;letter-spacing:.15em;
      text-transform:uppercase;color:var(--dlabel);
    }
    .railhead{
      display:block; margin-top:6px; padding-top:16px; border-top:1px solid var(--dline-soft);
      font-family:var(--mono); font-size:9.5px; letter-spacing:.16em; text-transform:uppercase; color:var(--dlabel);
    }
    .identity{flex:0 0 auto;padding:14px 15px;border:1px solid var(--dline);border-radius:var(--r-card);background:var(--dsurf)}
    .identity .biz{font-size:15.5px;white-space:normal;line-height:1.35}
    .identity .agent-name{margin-top:6px}
    .status{align-self:flex-start}
    .sidefoot{
      display:block; margin-top:auto; padding-top:16px; border-top:1px solid var(--dline-soft);
      font-family:var(--mono); font-size:10px; line-height:1.8; letter-spacing:.04em; color:var(--dlabel);
    }
    ul.docs{
      display:flex; flex-direction:column; gap:2px; min-height:0; overflow-y:auto;
      margin:0; padding:0; list-style:none;
    }
    ul.docs li{margin:0}
    ul.docs a{
      display:block; padding:7px 9px; border-radius:8px; text-decoration:none;
      border:1px solid transparent; color:var(--dsub); font-size:13px; line-height:1.35;
      overflow-wrap:anywhere;
    }
    ul.docs a:hover{border-color:var(--dline); background:var(--dsurf); color:var(--dtext)}
    ul.docs a small{display:block; font-family:var(--mono); font-size:9px; letter-spacing:.05em; color:var(--dlabel)}
    .docs-all{display:block; margin-top:4px; color:var(--accent); font:10px var(--mono); text-decoration:none}
    main.thread{padding:32px 44px 16px}
    form.composer{padding:15px 44px 17px}
    /* The hint explains the composer's own keyboard affordances, so it belongs over the
       conversation and nowhere else. It used to print under the composer on Invoices, Log, Today
       and Waiting too: a developer note on four screens that have no conversation on them. It is
       desktop-only for the same reason it exists at all — a soft keyboard has no Shift key. */
    body[data-tab="ask"] .hintrow{display:block}
    .wrap{gap:24px}
  }
  @media (min-width:1000px) and (max-width:1180px){
    main.thread,form.composer{padding-left:30px;padding-right:30px}
  }

  /* ---------- console shell ---------- */
  .mast-tools{display:flex;align-items:center;gap:8px;flex:0 0 auto;margin-left:auto}
  .mast-action{display:grid;place-items:center;width:32px;height:32px;padding:0;border:1px solid var(--dline);border-radius:9px;background:var(--dsurf);color:var(--dlabel)}
  .mast-action svg{width:16px;height:16px}
  .account-action{display:grid;place-items:center;width:32px;height:32px;padding:0;border:1px solid var(--dline);border-radius:9px;background:var(--dsurf);color:var(--dsub);cursor:pointer}
  .account-action svg{width:16px;height:16px}
  .mast-action{position:relative;cursor:pointer}
  .mast-action:hover{color:var(--dtext);border-color:var(--dline-strong)}
  .mast-badge,.tab-badge{display:inline-grid;place-items:center;min-width:16px;height:16px;padding:0 4px;border-radius:99px;background:var(--accent);color:#061011;font:9px var(--mono);font-weight:700}
  .mast-badge{position:absolute;top:-6px;right:-6px}
  .search-slot{display:flex;align-items:center;position:relative;width:184px}
  .search-slot input{width:100%;height:44px;padding:0 42px 0 30px;border:1px solid var(--dline);border-radius:9px;background:var(--dsurf);color:var(--dtext);font:11px var(--mono);letter-spacing:.02em}
  .search-slot input::placeholder{color:var(--dlabel)}
  .search-slot input:disabled{opacity:.62;cursor:default}
  .search-submit{position:absolute;right:0;top:0;min-width:44px;min-height:44px;padding:0;border:0;background:transparent;color:var(--dlabel);font-size:18px;cursor:pointer}
  .tabs{grid-area:tabs;display:flex;align-items:center;gap:4px;padding:10px 16px 0;border-bottom:1px solid var(--dline);background:rgba(3,8,10,.62);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}
  .tab{position:relative;display:inline-flex;align-items:center;min-height:36px;padding:0 13px;color:var(--dlabel);text-decoration:none;font-size:13px;font-weight:700;letter-spacing:-.01em}
  .tab:hover{color:var(--dtext)}
  .tab.is-on{color:var(--cloud)}
  .tab.is-on::after{content:"";position:absolute;right:10px;bottom:-1px;left:10px;height:2px;border-radius:2px 2px 0 0;background:var(--accent)}
  .panel-today{grid-area:thread;min-width:0;min-height:0;overflow:auto;padding:32px 44px 24px}
  .panel-ask{display:contents}
  .panel-ask[hidden]{display:none}
  .panel-today[hidden]{display:none}
  .today{width:min(100%,980px);margin:0 auto;padding-bottom:20px}
  .today-head{display:flex;justify-content:space-between;gap:24px;align-items:flex-end;margin-bottom:24px}
  .today-kicker,.today-label{margin:0;font:10px var(--mono);letter-spacing:.16em;text-transform:uppercase;color:var(--dlabel)}
  .today-title{margin:5px 0 0;color:var(--cloud);font-size:clamp(24px,3vw,34px);line-height:1.12;letter-spacing:-.045em}
  .today-state{display:flex;align-items:center;gap:7px;color:var(--dsub);font:11px var(--mono);white-space:nowrap}
  .today-state-dot{width:7px;height:7px;border-radius:50%;background:var(--dlabel)}
  .today-state-dot.is-up{background:var(--accent);box-shadow:0 0 12px rgba(79,196,178,.55)}
  .today-grid{display:grid;grid-template-columns:minmax(0,1.12fr) minmax(0,.88fr);gap:14px}
  .today-card{min-width:0;padding:18px;border:1px solid var(--dline);border-radius:var(--r-card);background:var(--dsurf)}
  .today-card-wide{grid-column:1/-1}
  .today-card h2{margin:4px 0 14px;color:var(--cloud);font-size:18px;line-height:1.25;letter-spacing:-.025em}
  .today-list{display:grid;gap:9px;margin:0;padding:0;list-style:none}
  .today-row{display:flex;justify-content:space-between;gap:16px;align-items:flex-start;padding:10px 0;border-top:1px solid var(--dline-soft)}
  .today-row:first-child{border-top:0;padding-top:0}
  .today-row:last-child{padding-bottom:0}
  .today-row-main{min-width:0}
  .today-row-title{display:block;color:var(--dtext);font-size:14px;line-height:1.4}
  .today-row-detail{display:block;margin-top:3px;color:var(--dsub);font-size:12px;line-height:1.45;overflow-wrap:anywhere}
  .today-row-time{flex:0 0 auto;color:var(--dlabel);font:10px var(--mono);white-space:nowrap}
  .today-more{margin:10px 0 0;color:var(--accent);font:11px var(--mono)}
  .today-empty{margin:0;color:var(--dsub);font-size:14px}
  .today-link{color:var(--accent);text-decoration:none}
  .today-link:hover{text-decoration:underline}
  .today-docs a{display:flex;align-items:center;justify-content:space-between;gap:12px}
  .today-docs .today-row-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .today-thread-link{display:inline-block;margin-top:12px;color:var(--accent);font-size:12px;font-weight:700;text-decoration:none}
  .today-thread-link:hover{text-decoration:underline}
  .panel-waiting{grid-area:thread;min-width:0;min-height:0;overflow:auto;padding:32px 44px 24px}
  .panel-waiting[hidden]{display:none}
  .waiting{width:min(100%,980px);margin:0 auto;padding-bottom:20px}
  .waiting-head{margin-bottom:24px}.waiting-kicker,.waiting-label{margin:0;color:var(--dlabel);font:10px var(--mono);letter-spacing:.16em;text-transform:uppercase}
  .waiting-title{margin:5px 0 0;color:var(--cloud);font-size:clamp(24px,3vw,34px);line-height:1.12;letter-spacing:-.045em}
  .waiting-card{margin-top:14px;padding:18px;border:1px solid var(--dline);border-radius:var(--r-card);background:var(--dsurf)}
  .waiting-card:first-of-type{margin-top:0}.waiting-group-detail{margin:7px 0 0;color:var(--dsub);font-size:12px;line-height:1.45}
  .waiting-list{display:grid;gap:9px;margin-top:14px}.waiting-row{display:flex;justify-content:space-between;gap:18px;padding:12px 0;border-top:1px solid var(--dline-soft)}
  .waiting-row:first-child{border-top:0;padding-top:0}.waiting-row:last-child{padding-bottom:0}.waiting-row-main{min-width:0}.waiting-row-title{margin:0;color:var(--dtext);font-size:14px;line-height:1.4}.waiting-row-detail{margin:4px 0 0;color:var(--dsub);font-size:12px;line-height:1.45;overflow-wrap:anywhere}.waiting-due{margin:6px 0 0;color:var(--accent);font:10px var(--mono)}
  .waiting-row-side{display:flex;flex:0 0 auto;align-items:flex-start;gap:10px}.waiting-row-time,.alert-time{color:var(--dlabel);font:10px var(--mono);white-space:nowrap}.waiting-choices{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px}.waiting-action,.alert-action,.alerts-mark{min-height:30px;padding:0 10px;border:1px solid var(--dline);border-radius:8px;background:transparent;color:var(--dsub);font:10px var(--mono);cursor:pointer}.alerts-mark[hidden],#alerts-badge[hidden]{display:none}.waiting-action:hover,.alert-action:hover,.alerts-mark:hover{border-color:var(--accent);color:var(--cloud)}
  .waiting-empty,.alerts-empty{margin:0;color:var(--dsub);font-size:14px}.alerts-panel{position:absolute;z-index:5;top:74px;right:16px;width:min(360px,calc(100vw - 32px));max-height:min(560px,calc(100vh - 96px));overflow:auto;padding:16px;border:1px solid var(--dline);border-radius:var(--r-card);background:rgba(10,18,21,.98);box-shadow:0 20px 60px rgba(0,0,0,.35)}.alerts-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}.alerts-head h2{margin:0;color:var(--cloud);font-size:15px}.alerts-list{display:grid;gap:8px}.alert-row{display:flex;gap:10px;padding:11px 0;border-top:1px solid var(--dline-soft)}.alert-row:first-child{border-top:0}.alert-main{min-width:0;flex:1}.alert-title{margin:0;color:var(--dtext);font-size:12px;line-height:1.4}.alert-body{margin:3px 0;color:var(--dsub);font-size:11px;line-height:1.45;overflow-wrap:anywhere}.alert-action{align-self:flex-start;white-space:nowrap}.alert-row.is-read{opacity:.68}
  .panel-log,.panel-search,.panel-invoices,.panel-documents{grid-area:thread;min-width:0;min-height:0;overflow:auto;padding:32px 44px 24px}
  .panel-log[hidden],.panel-search[hidden],.panel-invoices[hidden],.panel-documents[hidden]{display:none}
  .log,.search-results{width:min(100%,980px);margin:0 auto;padding-bottom:20px}
  .log-head,.search-head{margin-bottom:24px}.log-kicker,.search-kicker{margin:0;color:var(--dlabel);font:10px var(--mono);letter-spacing:.16em}.log-title,.search-title{margin:5px 0 0;color:var(--cloud);font-size:clamp(24px,3vw,34px);line-height:1.12;letter-spacing:-.045em}
  .log-day{margin-top:20px;padding:18px;border:1px solid var(--dline);border-radius:var(--r-card);background:var(--dsurf)}.log-day:first-of-type{margin-top:0}.log-day-head{display:flex;justify-content:space-between;gap:16px;align-items:baseline}.log-day-title{margin:0;color:var(--cloud);font-size:15px}.log-day-usage{margin:0;color:var(--accent);font:10px var(--mono);white-space:nowrap}.log-entry{padding:15px 0;border-top:1px solid var(--dline-soft)}.log-entry:first-of-type{border-top:0;padding-top:14px}.log-entry-at,.search-row-time{display:block;color:var(--dlabel);font:10px var(--mono)}.log-question{margin:6px 0 0;color:var(--dtext);font-size:15px;line-height:1.45}.log-answer{margin:8px 0 0;color:var(--dsub);font-size:14px;line-height:1.55;white-space:pre-wrap;overflow-wrap:anywhere}.log-meta{margin:8px 0 0;color:var(--dlabel);font:10px var(--mono);letter-spacing:.03em}.log-produced{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 0;padding:0;list-style:none}.log-produced li{padding:5px 8px;border:1px solid var(--dline);border-radius:6px;color:var(--dsub);font:10px var(--mono);overflow-wrap:anywhere}.log-empty,.search-empty{margin:0;color:var(--dsub);font-size:14px}.log-more{display:block;margin:16px auto 0;min-height:34px;padding:0 14px;border:1px solid var(--dline);border-radius:8px;background:transparent;color:var(--dsub);font:10px var(--mono);cursor:pointer}.log-more:hover{border-color:var(--accent);color:var(--cloud)}
  .search-group{margin-top:14px;padding:18px;border:1px solid var(--dline);border-radius:var(--r-card);background:var(--dsurf)}.search-group:first-of-type{margin-top:0}.search-group-title{margin:0;color:var(--cloud);font-size:15px}.search-list{display:grid}.search-row{padding:13px 0;border-top:1px solid var(--dline-soft)}.search-row:first-child{margin-top:10px}.search-row-title{margin:0;color:var(--dtext);font-size:14px;line-height:1.4}.search-row-detail{margin:4px 0;color:var(--dsub);font-size:13px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}.search-row-time{margin-top:6px}
  .invoices{width:min(100%,980px);margin:0 auto;padding-bottom:20px}.invoices-head{margin-bottom:24px}.invoices-kicker{margin:0;color:var(--dlabel);font:10px var(--mono);letter-spacing:.16em;text-transform:uppercase}.invoices-title{margin:5px 0 0;color:var(--cloud);font-size:clamp(24px,3vw,34px);line-height:1.12;letter-spacing:-.045em}.invoice-list{display:grid;gap:10px}.invoice-row,.invoice-detail{padding:17px 18px;border:1px solid var(--dline);border-radius:var(--r-card);background:var(--dsurf)}.invoice-row{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;width:100%;text-align:left;color:inherit;cursor:pointer}.invoice-row:hover{border-color:var(--accent)}.invoice-main{min-width:0}.invoice-number{margin:0;color:var(--cloud);font-size:15px}.invoice-meta,.invoice-copy,.invoice-muted{margin:5px 0 0;color:var(--dsub);font-size:12px;line-height:1.5;overflow-wrap:anywhere}.invoice-side{display:flex;align-items:flex-end;flex-direction:column;gap:7px;flex:0 0 auto}.invoice-total{color:var(--cloud);font:12px var(--mono);white-space:nowrap}.invoice-pill{display:inline-flex;padding:4px 7px;border:1px solid var(--dline);border-radius:99px;color:var(--accent);font:9px var(--mono);text-transform:uppercase;letter-spacing:.06em}.invoice-pill.is-paid{color:var(--accent)}.invoice-pill.is-overdue{color:#ffb18c;border-color:rgba(255,177,140,.45)}.invoice-pill.is-draft,.invoice-pill.is-sent,.invoice-pill.is-void,.invoice-pill.is-unknown{color:var(--dsub)}.invoice-empty{margin:0;color:var(--dsub);font-size:14px}.invoice-detail-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.invoice-back,.invoice-claim button{min-height:32px;padding:0 11px;border:1px solid var(--dline);border-radius:8px;background:transparent;color:var(--dsub);font:10px var(--mono);cursor:pointer}.invoice-back:hover,.invoice-claim button:hover{border-color:var(--accent);color:var(--cloud)}.invoice-lines{display:grid;margin:20px 0 0;border-top:1px solid var(--dline-soft)}.invoice-line{display:flex;justify-content:space-between;gap:14px;padding:11px 0;border-bottom:1px solid var(--dline-soft);color:var(--dtext);font-size:13px}.invoice-line small{display:block;color:var(--dsub);font-size:11px}.invoice-copy{white-space:pre-wrap}.invoice-claim{margin-top:20px;padding-top:18px;border-top:1px solid var(--dline-soft)}.invoice-claim h3{margin:0;color:var(--cloud);font-size:14px}.invoice-claim label{display:grid;gap:6px;margin-top:12px;color:var(--dlabel);font:10px var(--mono);text-transform:uppercase;letter-spacing:.08em}.invoice-claim input,.invoice-claim textarea{width:100%;padding:9px;border:1px solid var(--dline);border-radius:7px;background:var(--dsurf);color:var(--dtext);font:12px var(--mono)}.invoice-claim textarea{min-height:64px;resize:vertical}.invoice-claim button{margin-top:12px;color:var(--cloud)}.invoice-payment{padding:11px 0;border-top:1px solid var(--dline-soft);color:var(--dsub);font-size:12px;line-height:1.45}.invoice-status{margin:12px 0 0;color:var(--accent);font:11px var(--mono)}
  .documents{width:min(100%,980px);margin:0 auto;padding-bottom:20px;color:var(--dsub)}
  .documents-head{margin-bottom:24px}.documents-kicker{margin:0;color:var(--dlabel);font:10px var(--mono);letter-spacing:.16em}.documents-title{margin:5px 0 0;color:var(--dsub);font-size:clamp(24px,3vw,34px);line-height:1.12;letter-spacing:-.045em}.documents-summary{margin:8px 0 0;color:var(--dsub);font:11px var(--mono)}
  .documents-controls{display:flex;align-items:center;justify-content:space-between;gap:14px;margin-bottom:20px}.documents-filters{display:flex;flex-wrap:wrap;gap:6px}.documents-filter,.documents-controls input,.documents-more{min-height:40px;border:1px solid var(--dline);border-radius:var(--r-card);background:var(--dsurf);color:var(--dsub);font:10px var(--mono)}.documents-filter{padding:0 11px;cursor:pointer}.documents-filter[aria-pressed="true"],.documents-filter:hover,.documents-more:hover{border-color:var(--accent);color:var(--accent)}.documents-controls input{width:min(260px,100%);padding:0 11px}.documents-controls input::placeholder{color:var(--dlabel)}
  .document-month{margin-top:20px;padding:18px;border:1px solid var(--dline);border-radius:var(--r-card);background:var(--dsurf)}.document-month:first-child{margin-top:0}.document-month-title{margin:0;color:var(--dsub);font-size:15px}.document-row{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:15px 0;border-top:1px solid var(--dline-soft)}.document-row:first-of-type{margin-top:10px}.document-row-main{min-width:0}.document-kind{display:inline-flex;align-items:center;justify-content:center;width:34px;height:24px;margin-right:9px;border:1px solid var(--dline);border-radius:var(--r-card);color:var(--dlabel);font:9px var(--mono)}.document-name{color:var(--dsub);font-size:14px;line-height:1.4;overflow-wrap:anywhere}.document-meta{margin:6px 0 0;color:var(--dsub);font:10px var(--mono);overflow-wrap:anywhere}.document-note{margin:7px 0 0;color:var(--dsub);font-size:12px;line-height:1.45;overflow-wrap:anywhere}.document-download{flex:0 0 auto;color:var(--accent);font:10px var(--mono);text-decoration:none}.document-download:hover{text-decoration:underline}.documents-more{display:block;margin:16px auto 0;padding:0 14px;cursor:pointer}.documents-empty-copy{margin:0;color:var(--dsub);font-size:14px}.documents-chat-link{display:inline-block;margin-top:12px;color:var(--accent);font-size:13px;text-decoration:none}.documents-chat-link:hover{text-decoration:underline}
  @media (max-width:760px){
    .app{grid-template-rows:auto auto minmax(0,1fr) auto;grid-template-areas:"side" "tabs" "thread" "composer"}
    .tabs{padding-left:16px;padding-right:16px}
    .panel-today{padding:22px 16px 20px}
    .panel-waiting{padding:22px 16px 20px}
    .panel-log,.panel-search,.panel-invoices,.panel-documents{padding:22px 16px 20px}
    .today-head{align-items:flex-start;flex-direction:column;gap:10px;margin-bottom:18px}
    .today-grid{grid-template-columns:1fr}
    .today-card-wide{grid-column:auto}
  }
  @media (min-width:1000px){
    .app{grid-template-columns:264px 1fr;grid-template-rows:auto 1fr auto;grid-template-areas:"side tabs" "side thread" "side composer"}
    .tabs{padding-left:44px;padding-right:44px}
    .side .mast-tools{width:100%;margin-left:0}
    .side .search-slot{flex:1 1 auto;width:auto}
    .panel-today{padding-left:44px;padding-right:44px}
    .panel-waiting{padding-left:44px;padding-right:44px}
    .panel-log,.panel-search,.panel-invoices,.panel-documents{padding-left:44px;padding-right:44px}
    .side{position:relative}
  }
  @media (min-width:1000px) and (max-width:1180px){
    .tabs{padding-left:30px;padding-right:30px}
    .panel-today{padding-left:30px;padding-right:30px}
    .panel-waiting{padding-left:30px;padding-right:30px}
    .panel-log,.panel-search,.panel-invoices,.panel-documents{padding-left:30px;padding-right:30px}
  }
  body.reading-old .app{grid-template-rows:auto auto 1fr;grid-template-areas:"side tabs" "side thread" "side thread"}
  body.reading-old .tabs{display:flex}
  @media (max-width:760px){body.reading-old .app{grid-template-rows:auto auto 1fr;grid-template-areas:"side" "tabs" "thread"}}

  /* portal redesign: controls remain comfortable on a finger and the two header popovers stay
     attached to their controls instead of covering the bell. */
  .composer[hidden]{display:none}
  .tabs{overflow-x:auto;overscroll-behavior-x:contain;scrollbar-width:none;white-space:nowrap}
  .tabs::-webkit-scrollbar{display:none}
  .tab{min-height:44px;flex:0 0 auto}
  .mast-tools{position:relative}
  .mast-action,.account-action{min-width:44px;min-height:44px;width:auto}
  .account-action{padding:0}
  .account-menu{position:absolute;z-index:8;top:calc(100% + 8px);right:0;width:min(280px,calc(100vw - 24px));padding:16px;border:1px solid var(--dline);border-radius:var(--r-card);background:rgba(10,18,21,.99);box-shadow:0 20px 60px rgba(0,0,0,.35)}
  .is-target{outline:2px solid var(--accent);outline-offset:4px;scroll-margin-block:35vh}
  .focus-failure{margin:12px 0;padding:10px 12px;border:1px solid var(--dline);border-radius:8px;color:var(--dsub);font-size:13px}
  .account-email,.account-business,.account-role{margin:0;color:var(--dtext);font-size:12px;overflow-wrap:anywhere}.account-business,.account-role{margin-top:5px;color:var(--dsub);font-size:11px}.account-signout,.alerts-close,.search-close{min-width:44px;min-height:44px;border:1px solid var(--dline);border-radius:8px;background:transparent;color:var(--dsub);font:10px var(--mono);cursor:pointer}.account-signout{width:100%;margin-top:14px}.account-signout:hover,.alerts-close:hover,.search-close:hover{border-color:var(--accent);color:var(--cloud)}
  .search-slot{position:relative;min-width:44px}.search-icon{position:absolute;left:11px;top:11px;color:var(--dlabel);font-size:18px;line-height:1;pointer-events:none}.search-slot input{height:44px;padding-left:30px}
  /* Owner report 2026-09-19: "on PC the notifications go off the screen to the left, you can't read
     them." The panel hung off the tools cluster's RIGHT edge, which in the two-column layout
     (>=1000px) sits inside the 264px rail, so a 380px panel ran past the viewport's left edge
     (measured: -6px at 768-999px, -191px at 1024-1920px). It now hangs off the cluster's LEFT edge
     and opens rightward over the content column: measured at x=22 for >=1000px and x=66 for the
     full-width header below that, always fully inside the viewport. The account menu keeps its
     right-anchored form down there because 280px fits from the cluster's right edge, and flips at
     1000px to match. Declared after both popovers on purpose: media queries add no specificity, so
     source order is what flips them.
     The 220px reserve is for short viewports: at 844x390 the header ends around y=150, so a 70vh
     panel ran off the bottom edge. Fixed positioning is not an option here — aside.side carries
     backdrop-filter:blur(12px), which makes it the containing block for fixed children, so a fixed
     panel resolves against the 32x250 rail instead of the viewport (measured: panel at y=-56). */
  .alerts-panel{position:absolute;top:calc(100% + 8px);left:0;right:auto;width:min(380px,calc(100vw - 32px));max-height:min(70vh,calc(100vh - 220px));overflow:auto;padding:16px}
  @media (min-width:1000px){.account-menu{left:0;right:auto}}
  /* .side carries backdrop-filter:blur(12px), so it is a stacking context: no z-index on the panel
     itself can lift it above later siblings (.tabs z-index 21, .composer). Raising .side while a
     popover is open is what actually puts the dropdown on top — measured at 844x390, the composer
     input was hit-testing over the open panel before this. */
  .side:has(.alerts-panel:not([hidden])),.side:has(.account-menu:not([hidden])){z-index:40}
  .alerts-panel:focus{outline:none}.alerts-head{align-items:flex-start}.alerts-head-actions{display:flex;align-items:center;gap:6px}.alerts-count{margin:3px 0 0;color:var(--dlabel);font:10px var(--mono)}.alerts-mark{min-height:44px}.alerts-close{font-size:18px;line-height:1}.alert-row{align-items:flex-start}.alert-dot{width:7px;height:7px;margin-top:6px;border-radius:50%;background:var(--accent);flex:0 0 auto}.alert-row.is-read .alert-dot{display:none}.alert-action{min-height:44px}
  .needs-strip{display:flex;align-items:center;justify-content:space-between;gap:12px;max-width:768px;margin:18px auto 0;padding:12px 16px;border:1px solid rgba(79,196,178,.4);border-radius:var(--r-card);background:#0d1a19;color:var(--dtext);font-size:13px}.needs-strip[hidden]{display:none}.needs-strip-link{min-height:44px;display:inline-flex;align-items:center;color:var(--accent);font-weight:700;text-decoration:none}
  .today-more{display:inline-flex;min-height:44px;align-items:center}.today-summary{margin:0;color:var(--dsub);font-size:14px}.today-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.today-stat{display:flex;min-height:76px;flex-direction:column;justify-content:center;gap:5px;padding:10px;border:1px solid var(--dline);border-radius:9px;color:var(--dtext);text-decoration:none}.today-stat:hover{border-color:var(--accent)}.today-stat strong{font-size:24px;line-height:1;color:var(--cloud)}.today-stat span{color:var(--dsub);font:10px var(--mono);line-height:1.25}.today-example{margin:10px 0 0;color:var(--dsub);font-size:13px;line-height:1.5}.today-docs .today-row-title{overflow-wrap:anywhere;white-space:normal;text-overflow:clip}.search-ask{display:inline-flex;align-items:center;min-height:44px;margin-top:12px;color:var(--accent);font-size:13px;font-weight:700}
  .search-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.search-head-copy{min-width:0}.search-count{margin:0 0 14px;color:var(--dlabel);font:10px var(--mono)}.search-row{display:block;min-height:44px;color:inherit;text-decoration:none}.search-row:hover{background:var(--dsurf)}
  .waiting-action,.invoice-back,.invoice-claim button,.log-more{min-height:44px}
  .backdrop::after{opacity:.22;transform:translateY(18vh)}
  .biz{white-space:normal;overflow:visible;text-overflow:clip;overflow-wrap:anywhere}
  @media (max-width:760px){.side{gap:8px;align-items:flex-start;flex-wrap:wrap}.status{padding:2px 0;min-height:0;border:0;background:transparent}.mast-tools{gap:4px;flex:1 1 100%;width:100%;min-width:0;margin-left:0;flex-wrap:wrap;justify-content:flex-end}.search-slot{order:5;flex:1 1 100%;width:100%;min-width:0}.today-grid{gap:12px}.today-card{padding:16px}.today-stats{gap:6px}.today-stat{min-height:72px;padding:9px 8px}.panel-ask{overflow:auto}.needs-strip{margin:12px 16px 0}.composer{min-width:0}}
  @media (min-width:1000px) and (max-width:1240px){.search-slot{width:150px}}
  .panel-ask{display:flex;flex-direction:column;min-height:0;overflow:hidden}.panel-ask main.thread{flex:1 1 auto;min-height:0}

  /* ---------- phone app shell ---------- */
  .tab-icon{display:contents}
  .tab-icon svg{display:none}
  @media (max-width:760px){
    .app{
      grid-template-rows:auto minmax(0,1fr) auto auto;
      grid-template-areas:"side" "thread" "composer" "tabs";
    }

    .side{
      display:grid; grid-template-columns:auto minmax(0,1fr) 44px 44px;
      grid-template-rows:auto 44px; column-gap:0; row-gap:4px;
      align-items:center; padding: max(6px, env(safe-area-inset-top)) 16px 6px;
      background:var(--ink); backdrop-filter:none; -webkit-backdrop-filter:none;
    }
    .brand{grid-column:1;grid-row:1;gap:0}
    .mark{font-size:26px}
    .identity{grid-column:2;grid-row:1;min-width:0;align-self:center}
    .biz{font-size:16px;line-height:1.15;letter-spacing:-.02em;white-space:normal;overflow:visible;text-overflow:clip}
    .identity-meta{display:flex;align-items:center;min-width:0;line-height:1.2}
    .agent-name{display:inline;min-width:0;margin:0;overflow:visible;text-overflow:clip;font-size:11px;letter-spacing:.04em;text-transform:none;white-space:nowrap}
    .status{display:flex;align-items:center;gap:6px;min-width:0;padding:0;border:0;background:transparent;font-size:11px;letter-spacing:.02em;text-transform:none;white-space:nowrap}
    .status::before{content:"·";margin:0 6px;color:var(--dlabel)}
    .dot{width:7px;height:7px}
    .mast-tools{display:contents}
    .mast-action{grid-column:3;grid-row:1;width:44px;height:44px}
    .mast-action svg{width:20px;height:20px}
    .account-action{grid-column:4;grid-row:1;width:44px;height:44px;padding:0}
    .account-action svg{width:20px;height:20px}
    .search-slot{grid-column:1/-1;grid-row:2;width:100%;min-width:0;height:44px}
    .search-slot input{height:44px;padding:0 44px 0 12px;font-size:16px}
    .search-icon{display:none}

    .tabs{
      position:relative; z-index:21;
      min-height:56px; height:auto; padding:0 0 env(safe-area-inset-bottom);
      border:0; border-top:1px solid var(--dline); background:var(--ink);
      backdrop-filter:none; -webkit-backdrop-filter:none; overflow:hidden; gap:0;
    }
    .tab{
      flex:1 1 0; min-width:44px; min-height:56px; height:56px; padding:4px 2px 3px;
      display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px;
      color:var(--dlabel); font-size:11px; line-height:1.1; letter-spacing:0;
    }
    .tab:hover{color:var(--dlabel)}
    .tab.is-on{color:var(--accent)}
    .tab.is-on::after{display:none}
    /* A colour change alone is a weak active state on a phone: it is the one piece of the shell
       that says where you are, and every platform convention for a bottom bar (Material 3, iOS,
       Vercel's 2026 bar) fills the active item rather than tinting its text. The pill sits inside
       the tab's own 56px box, so the tap target is unchanged. */
    .tab.is-on::before{content:"";position:absolute;inset:6px 8px 5px;border-radius:12px;background:var(--glow);border:1px solid rgba(79,196,178,.24)}
    .tab-icon,.tab-label{position:relative;z-index:1}
    .tab-icon{position:relative;display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px}
    .tab-icon svg{display:block;width:22px;height:22px;stroke:currentColor}
    .tab-badge{position:absolute;top:-5px;right:-8px;min-width:16px;height:16px;padding:0 4px;font-size:9px}
    .tab-label{display:block;font-size:11px;line-height:1.1}

    form.composer{min-width:0;padding:8px 16px 10px;gap:4px}
    .field{min-width:0}

    .account-menu,.alerts-panel{
      position:fixed; z-index:30; left:0; right:0; bottom:0; top:auto;
      width:100%; max-height:78vh; overflow:auto; border:0; border-top:1px solid var(--dline);
      border-radius:18px 18px 0 0; padding:16px 16px calc(16px + env(safe-area-inset-bottom));
      background:var(--dpanel); box-shadow:0 -100vmax 0 100vmax rgba(0,0,0,.55);
    }
    .account-menu{right:auto}
    /* Owner report follow-up 2026-09-19: the sheet is the scroll container for a 49-item alert list,
       so it has to own its own touch. With pointer-events:none on the panel/list/rows a drag over
       the sheet passed straight through to the page behind — the list could not be scrolled and the
       rows were unreachable — while the scrim still looked interactive. The sheet keeps its own
       events now; the tab bar stays shielded because the sheet's box covers it. */
    .alerts-panel .alerts-head-actions,.alerts-panel .alert-action{pointer-events:auto}

    .panel-today,.panel-waiting,.panel-log,.panel-search,.panel-invoices,.panel-documents{padding:16px 16px 20px}
    .documents-controls{align-items:stretch;flex-direction:column}.documents-controls input{width:auto}
    .today-head,.waiting-head,.log-head,.search-head,.invoices-head{margin-bottom:18px}
    .today-head{gap:0}
    .today-kicker,.waiting-kicker,.log-kicker,.search-kicker,.invoices-kicker,.today-label,.waiting-label{
      font-size:11px; line-height:1.2; letter-spacing:.12em
    }
    .today-title,.waiting-title,.log-title,.search-title,.invoices-title{
      margin-top:6px; font-size:clamp(22px,6.4vw,28px); line-height:1.15; letter-spacing:-.03em
    }
    .today-grid{gap:12px}
    .today-card,.waiting-card,.log-day,.search-group,.invoice-row,.invoice-detail{padding:16px;border-radius:14px}
    .today-card h2{margin:6px 0 18px}
    .today-list,.waiting-list{gap:0}
    .today-row,.waiting-row{gap:12px;padding:12px 0}
    .today-row:first-child,.waiting-row:first-child{padding-top:0}
    .today-row:last-child,.waiting-row:last-child{padding-bottom:0}
    .today-row-title,.waiting-row-title,.search-row-title{font-size:15px;line-height:1.35}
    .today-row-detail,.waiting-row-detail,.search-row-detail{margin-top:4px;font-size:13px;line-height:1.45}
    .today-row-time,.waiting-row-time,.alert-time,.log-entry-at,.search-row-time{font-size:11px;line-height:1.25}
    .alert-title{font-size:13px;font-weight:650;color:var(--cloud)}
    .alert-body{font-size:12px;color:var(--dsub)}
    .alert-time{font:11px var(--mono);color:var(--dlabel)}
    .today-more,.search-ask{min-height:44px}
    .waiting-card{margin-top:12px}
    .waiting-list{margin-top:12px}
    .waiting-row-side{gap:8px}
    .log-day{margin-top:12px}
    .log-entry{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:8px;padding:12px 0}
    .log-entry:first-of-type{padding-top:0}
    .log-entry-at{grid-column:2;grid-row:1;display:block;text-align:right}
    .log-question{grid-column:1;grid-row:1;margin:0;font-size:15px;line-height:1.35}
    .log-answer,.log-meta,.log-produced{grid-column:1/-1}
    .log-answer{margin-top:8px}
    .log-more,.invoice-back,.invoice-claim button,.waiting-action,.alert-action,.alerts-mark,.search-close{min-height:44px}
    .log-produced li{font-size:11px}
    .search-group{margin-top:12px}
    .search-group-title{margin-bottom:8px}
    .search-row{position:relative;min-height:44px;padding:12px 0;padding-right:72px}
    .search-row:first-child{margin-top:0}
    .search-row-time{position:absolute;top:12px;right:0;margin:0;text-align:right}
    .invoice-list{gap:12px}
    .invoice-row{min-height:44px;gap:12px}
    .invoice-side{display:grid;justify-items:end;align-items:start;gap:4px}
    .invoice-pill{padding:3px 7px;line-height:1.2}
    .invoice-meta{font-size:13px}
    .invoice-back{padding:0 11px}
    .copy{display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:44px}
    .needs-strip{
      position:absolute; z-index:2; top:4px; left:16px; right:16px; width:auto; max-width:none;
      height:44px; min-height:44px; margin:0; padding:0 12px; border-radius:14px;
      font-size:13px; white-space:nowrap; overflow:visible
    }
    .needs-strip-link{min-height:44px;padding:0 4px}
    .panel-ask{position:relative}
    .panel-ask:has(.needs-strip:not([hidden])) main.thread{padding-top:64px}

    body.reading-old .app{
      grid-template-rows:auto minmax(0,1fr) auto;
      grid-template-areas:"side" "thread" "tabs";
    }
    body.reading-old .tabs{display:flex}
  }
  @media (max-width:340px){
    .biz{font-size:15px;letter-spacing:-.015em}
  }

  /* ---------- the touch floor, deliberately last ----------
     A focused text control that computes under 16px makes a phone's browser zoom the page on tap;
     iOS Safari and Samsung Internet both do it. This is the whole set of controls in the portal, so
     no field can zoom the page — and it is the LAST rule in this sheet on purpose. It has the same
     specificity as the compact class-scoped rules above (`.search-slot input` at 11px,
     `.invoice-claim input/textarea` at 12px), so an equal-specificity rule written after it would
     win: the first attempt at this sat next to the search rules and left the invoice claim fields
     at 12px on touch, which codex-review caught as a P1. `portal_mobile_check.mjs` asserts the
     ordering, not just the outcome, so the floor cannot be demoted silently.

     `any-pointer:coarse` and `hover:none` are OR'd in with `pointer:coarse` because a phone running
     Request Desktop Website narrows the viewport away while staying a touch device, and a hybrid
     laptop reports a fine *primary* pointer next to its touch screen. Desktop with a mouse matches
     none of the three, so the 11px Geist Mono rail is untouched. */
  @media (pointer:coarse), (any-pointer:coarse), (hover:none){
    input, textarea, select, .search-slot input, .invoice-claim input, .invoice-claim textarea{font-size:16px}
  }
/* Invoice source sections. Keep the rows readable when the portal narrows to a phone. */
.invoice-sections { display: grid; gap: 2rem; }
.invoice-section-title { margin: 0 0 .75rem; }
.invoice-section-list { display: grid; gap: .65rem; }
.invoice-empty { color: var(--muted, #6b7280); margin: 0; }
.invoice-row-main { min-width: 0; }
.invoice-row-side { min-width: 6rem; }
.invoice-issuer, .invoice-counterparty, .invoice-meta { overflow-wrap: anywhere; }
.invoice-overdue { color: var(--danger, #b42318); font-size: .82rem; font-weight: 700; }
.invoice-document { white-space: nowrap; }
.invoice-detail-info { display: grid; gap: .35rem; }
.invoice-detail-info p { margin: 0; }
@media (max-width: 520px) {
  .invoice-row { align-items: flex-start; gap: .75rem; }
  .invoice-row-main, .invoice-row-side { min-width: 0; }
  .invoice-row-side { flex: 0 1 7rem; text-align: right; }
  .invoice-row-side .invoice-document { display: block; white-space: normal; }
  .invoice-detail-head { flex-wrap: wrap; gap: .5rem; }
  .invoice-detail-title { min-width: 0; overflow-wrap: anywhere; }
}
