/* BitChatVault — enterprise blue.
   Same design language as the AgenTorQ site-v2 stage 3 system: Archivo on display=block
   so headings never reflow on a late font swap, Instrument Sans for reading text,
   JetBrains Mono for anything a machine wrote. Tokens carry the same role names so the
   two products read as one family. */

:root{
  /* palette — roles, not hues */
  --navy:#0a1733; --navy-2:#081230; --navy-3:#13285a; --abyss:#050d22;
  --paper:#f4f6fb;
  --brand:#2f63f0; --brand-strong:#1740b0; --brand-bright:#7ea6ff;
  --sky:#a9c2ff; --lagoon:#38a3ff;
  --saffron:#ffb020; --on-saffron:#0a1733;
  --signal:#f8330b; --coral-soft:#ff6a3d;
  --lime:#3fae6a;

  /* dark surfaces */
  --bg:var(--abyss);
  --surface:#0a1633;
  --surface-2:#0f1e42;
  --card:#0b1a3c;
  --line:#1b2d5c;
  --line-2:#26406f;

  --ink:#eef3ff; --ink-2:#a9b8dc; --muted:#7a89ad; --faint:#5d6b8c;

  --grad-brand:linear-gradient(90deg,#2f63f0,#38a3ff);
  --grad-hot:linear-gradient(90deg,#ffb020,#f8330b);

  --font-head:"Archivo","Archivo Fallback",-apple-system,Segoe UI,Arial,sans-serif;
  --font-body:"Instrument Sans","Instrument Sans Fallback",-apple-system,Segoe UI,Roboto,Arial,sans-serif;
  --font-mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;

  --r:14px; --r-sm:10px; --pad:16px;
  --shadow:0 1px 2px rgba(0,0,0,.35), 0 8px 28px rgba(0,0,0,.28);
}

*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--bg);color:var(--ink);
  font-family:var(--font-body);font-size:15px;line-height:1.6;
  font-variant-numeric:tabular-nums;
  background-image:
    radial-gradient(900px 480px at 78% -12%, rgba(47,99,240,.16), transparent 60%),
    radial-gradient(700px 420px at -8% 4%, rgba(56,163,255,.10), transparent 62%);
  background-attachment:fixed;
}
h1,h2,h3,.logo,.hero h1{font-family:var(--font-head);font-weight:700;letter-spacing:-.022em}
a{color:var(--brand-bright);text-decoration:none}
code,.mono{font-family:var(--font-mono);font-size:.92em}

/* ---------- shell ---------- */
.wrap{max-width:1040px;margin:0 auto;padding:var(--pad);
  padding-bottom:calc(48px + env(safe-area-inset-bottom))}
header{position:sticky;top:0;z-index:30;
  background:color-mix(in srgb, var(--abyss) 88%, transparent);
  backdrop-filter:blur(14px) saturate(140%);
  border-bottom:1px solid var(--line)}
.hd{display:flex;align-items:center;gap:12px;padding:13px var(--pad);max-width:1040px;margin:0 auto}
.logo{display:flex;align-items:center;gap:10px;font-weight:700;font-size:16px;letter-spacing:-.02em}
.mark{width:28px;height:28px;border-radius:8px;background:var(--grad-brand);
  display:grid;place-items:center;font-size:11px;font-weight:800;color:#fff;
  font-family:var(--font-head);letter-spacing:.02em;
  box-shadow:0 2px 10px rgba(47,99,240,.45)}
.tenant{margin-left:auto;font-size:11px;color:var(--muted);text-align:right;line-height:1.4}
.tenant b{color:var(--ink);display:block;font-size:13px;font-weight:600}

nav{display:flex;gap:4px;overflow-x:auto;padding:0 var(--pad) 10px;
  max-width:1040px;margin:0 auto;scrollbar-width:none}
nav::-webkit-scrollbar{display:none}
nav button{flex:0 0 auto;background:transparent;border:1px solid transparent;color:var(--muted);
  padding:8px 14px;border-radius:99px;font:600 13px/1 var(--font-body);cursor:pointer;
  white-space:nowrap;transition:color .14s,background .14s,border-color .14s}
nav button:hover{color:var(--ink-2)}
nav button.on{background:var(--surface-2);border-color:var(--line-2);color:var(--ink)}

/* ---------- cards ---------- */
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--r);
  padding:20px;margin-bottom:14px;box-shadow:var(--shadow)}
.card h2{margin:0 0 5px;font-size:17px;letter-spacing:-.02em}
.card p.sub{margin:0 0 16px;color:var(--muted);font-size:13.5px;line-height:1.55}
.card.accent{border-color:var(--brand);
  background:linear-gradient(180deg, rgba(47,99,240,.10), transparent 55%), var(--card)}

/* ---------- hero ---------- */
.hero{padding:44px 4px 26px}
.eyebrow{display:inline-flex;align-items:center;gap:7px;font:700 11px/1 var(--font-body);
  letter-spacing:.14em;text-transform:uppercase;color:var(--brand-bright);margin-bottom:16px;
  padding:6px 11px;border:1px solid var(--line-2);border-radius:99px;background:var(--surface)}
.eyebrow::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--lime);
  box-shadow:0 0 0 3px rgba(63,174,106,.18)}
.hero h1{font-size:30px;line-height:1.12;margin:0 0 14px;letter-spacing:-.035em;font-weight:800}
.hero p{color:var(--ink-2);font-size:15.5px;line-height:1.62;margin:0;max-width:56ch}
@media(min-width:760px){.hero{padding:64px 4px 34px}.hero h1{font-size:46px}.hero p{font-size:17px}}

.proof{display:grid;gap:10px;margin-bottom:16px}
@media(min-width:680px){.proof{grid-template-columns:repeat(3,1fr)}}
.pf{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-sm);padding:15px 16px}
.pf b{display:block;font:700 13.5px/1.3 var(--font-head);margin-bottom:5px;letter-spacing:-.01em}
.pf span{color:var(--muted);font-size:12.5px;line-height:1.5;display:block}

/* ---------- forms ---------- */
label{display:block;font:600 12px/1 var(--font-body);color:var(--muted);margin:14px 0 7px;
  letter-spacing:.01em}
input,select,textarea{width:100%;background:var(--input);border:1px solid var(--line);
  color:var(--ink);border-radius:var(--r-sm);padding:12px 13px;font:400 16px/1.4 var(--font-body);
  transition:border-color .14s, box-shadow .14s}
input::placeholder,textarea::placeholder{color:var(--faint)}
input:focus,select:focus,textarea:focus{outline:0;border-color:var(--brand);
  box-shadow:0 0 0 3px rgba(47,99,240,.22)}
textarea{min-height:110px;resize:vertical;font-size:14.5px}

button.btn{background:var(--brand);border:1px solid var(--brand);color:var(--on-brand,#fff);
  padding:12px 18px;border-radius:var(--r-sm);font:600 15px/1 var(--font-body);
  cursor:pointer;width:100%;margin-top:16px;transition:background .14s,transform .06s,box-shadow .14s;
  box-shadow:0 2px 12px rgba(47,99,240,.3)}
button.btn:hover{background:var(--brand-strong)}
button.btn:active{transform:translateY(1px)}
button.btn.ghost{background:transparent;border-color:var(--line-2);color:var(--ink);box-shadow:none}
button.btn.ghost:hover{background:var(--surface-2);border-color:var(--brand)}
button.btn.danger{background:var(--signal);border-color:var(--signal);box-shadow:0 2px 12px rgba(248,51,11,.3)}
button.btn.sm{width:auto;padding:9px 14px;font-size:13px;margin:0}

.row{display:flex;gap:9px;flex-wrap:wrap;align-items:center}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:11px}
@media(max-width:560px){.grid2{grid-template-columns:1fr}}

/* ---------- lists ---------- */
.item{border:1px solid var(--line);border-radius:var(--r-sm);padding:14px;margin-bottom:9px;
  background:var(--surface);transition:border-color .14s,background .14s}
.item .t{font:600 14px/1.4 var(--font-body);display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.item .m{color:var(--muted);font-size:12.5px;margin-top:5px;word-break:break-word;line-height:1.5}
.item.pick{cursor:pointer}
.item.pick:hover{border-color:var(--brand);background:var(--surface-2)}

.tag{font:700 10px/1 var(--font-body);letter-spacing:.07em;text-transform:uppercase;
  padding:4px 8px;border-radius:5px;background:var(--surface-2);color:var(--muted)}
.tag.conf{background:rgba(255,176,32,.14);color:var(--saffron)}
.tag.restr{background:rgba(248,51,11,.14);color:var(--coral-soft)}
.tag.intl{background:rgba(56,163,255,.14);color:var(--lagoon)}
.tag.ok{background:rgba(63,174,106,.16);color:var(--lime)}
.tag.dead{background:rgba(248,51,11,.16);color:var(--coral-soft)}
.tag.cap{background:rgba(47,99,240,.18);color:var(--brand-bright)}

.empty{color:var(--muted);font-size:13px;text-align:center;padding:30px 12px}
.note{font-size:12.5px;color:var(--ink-2);background:var(--surface);border:1px solid var(--line);
  border-left:3px solid var(--brand);border-radius:var(--r-sm);padding:13px 14px;margin-top:12px;line-height:1.6}
.kv{display:flex;justify-content:space-between;gap:10px;padding:9px 0;
  border-bottom:1px solid var(--line);font-size:13.5px}
.kv:last-child{border:0}
.kv span:first-child{color:var(--muted)}
.kv span:last-child{font-weight:600;font-variant-numeric:tabular-nums}

/* ---------- sign-in providers ---------- */
.sep{display:flex;align-items:center;gap:12px;margin:18px 0 6px;color:var(--faint);
  font:600 11px/1 var(--font-body);letter-spacing:.09em;text-transform:uppercase}
.sep::before,.sep::after{content:'';flex:1;height:1px;background:var(--line)}
.btn.prov{display:flex;align-items:center;justify-content:center;gap:9px;font-size:14px;font-weight:600}
.gmark{width:18px;height:18px;border-radius:50%;background:#fff;color:#4285f4;
  font:800 12px/1 var(--font-head);display:grid;place-items:center;flex:0 0 auto}
.mmark{width:16px;height:16px;flex:0 0 auto;
  background:linear-gradient(90deg,#f25022 50%,#7fba00 50%) top/100% 50% no-repeat,
             linear-gradient(90deg,#00a4ef 50%,#ffb900 50%) bottom/100% 50% no-repeat}

.feat{border-top:1px solid var(--line);padding:14px 0;color:var(--muted);font-size:13.5px;line-height:1.6}
.feat:first-of-type{border-top:0;padding-top:4px}
.feat b{display:block;color:var(--ink);font:600 14.5px/1.4 var(--font-head);margin-bottom:4px;
  letter-spacing:-.012em}
.feat code{background:var(--surface-2);padding:2px 7px;border-radius:5px;font-size:12px;
  color:var(--brand-bright)}
.foot{text-align:center;color:var(--faint);font-size:12px;padding:26px 0 40px}

.slugmsg{font-size:12.5px;margin-top:7px;color:var(--muted);min-height:18px;font-weight:500}
.slugmsg.ok{color:var(--lime)} .slugmsg.bad{color:var(--coral-soft)}

/* ---------- toast ---------- */
#toast{position:fixed;left:50%;transform:translateX(-50%);bottom:26px;z-index:100;
  background:var(--surface-2);border:1px solid var(--line-2);border-radius:var(--r-sm);
  padding:13px 17px;font-size:14px;max-width:min(92vw,520px);box-shadow:var(--shadow);display:none}
#toast.show{display:block;animation:toastUp .2s ease}
#toast.ok{border-color:var(--lime)} #toast.bad{border-color:var(--signal)}
@keyframes toastUp{from{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%,0)}}

/* ---------- viewer ---------- */
/* theme-fixed: a document reader is a lightbox. The surround stays near-black
   in every palette so the page is the only bright thing on the screen, which
   is the whole point of the surface. */
#viewer{position:fixed;inset:0;z-index:60;background:var(--abyss);display:none;flex-direction:column}
#viewer.on{display:flex}
.vtop{display:flex;align-items:center;gap:10px;padding:13px 15px;border-bottom:1px solid var(--line);
  background:var(--surface);padding-top:calc(13px + env(safe-area-inset-top))}
.vtop .nm{font:600 14px/1.3 var(--font-body);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.vcaps{display:flex;gap:6px;padding:9px 15px;border-bottom:1px solid var(--line);
  overflow-x:auto;background:var(--surface);scrollbar-width:none}
.vcaps::-webkit-scrollbar{display:none}
/* A capability chip is an icon and a word, not shouting. "NO FORWARD" in caps
   read as an error; the icon carries the meaning and the word confirms it. */
.cap{display:inline-flex;align-items:center;gap:5px;font:600 11.5px/1 var(--font-body);
  padding:6px 10px;border-radius:7px;white-space:nowrap;letter-spacing:.01em}
.cap svg{width:14px;height:14px;flex:0 0 auto}
.cap b{font-weight:600}
.cap.n{opacity:.72}
.cap.n svg{opacity:.8}
.vcaps .capgo{margin-left:auto;flex:0 0 auto}
.cap.y{background:rgba(63,174,106,.16);color:var(--lime)}
.cap.n{background:rgba(248,51,11,.14);color:var(--coral-soft)}
#stage{flex:1;overflow:auto;display:grid;place-items:start center;padding:18px}
/* theme-fixed: this is the PAPER. A document renders as the document, not as
   the app's theme -- a contract printed on Forest green would be a different
   document from the one somebody signed. */
.page{position:relative;background:var(--paper);color:var(--navy);width:100%;max-width:780px;
  border-radius:var(--r-sm);padding:30px 26px;font:400 13px/1.7 var(--font-mono);
  white-space:pre-wrap;word-break:break-word;min-height:55vh;
  user-select:none;-webkit-user-select:none;box-shadow:0 10px 40px rgba(0,0,0,.4)}
.page.cancopy{user-select:text;-webkit-user-select:text}
.wm{position:absolute;inset:0;pointer-events:none;overflow:hidden;border-radius:var(--r-sm)}
.vbot{display:flex;align-items:center;gap:10px;padding:11px 15px;border-top:1px solid var(--line);
  background:var(--surface);padding-bottom:calc(11px + env(safe-area-inset-bottom))}
.vbot .pg{flex:1;text-align:center;font-size:13px;color:var(--muted);font-variant-numeric:tabular-nums}
.vzoom{display:flex;align-items:center;gap:4px;flex:0 0 auto}
.vzoom .zlab{min-width:44px;text-align:center;font:600 12px/1 var(--font-mono);color:var(--ink-2)}
.vzoom .ibtn{width:30px;height:30px}
.vzoom .ibtn.on{background:var(--brand);color:#fff}
.vzoom .ibtn[disabled]{opacity:.35;pointer-events:none}
@media (max-width:640px){.vzoom .zlab{min-width:38px;font-size:11px}}
.deny{padding:44px 20px;text-align:center}
.deny h3{color:var(--signal);margin:0 0 9px;font-family:var(--font-head)}
.deny code{display:block;margin-top:12px;color:#5d6b8c;font-size:12px;word-break:break-all}

/* ---------- chat surface ---------- */
.seclabel{font:700 11px/1 var(--font-body);letter-spacing:.12em;text-transform:uppercase;
  color:var(--faint);margin:0 0 12px}
.badge{background:var(--brand);color:#fff;font:700 11px/1 var(--font-body);
  padding:4px 8px;border-radius:99px;margin-left:auto;min-width:22px;text-align:center}
.msg{padding:12px 0;border-top:1px solid var(--line)}
.msg:first-child{border-top:0;padding-top:2px}
.msg .t{font:600 13.5px/1.4 var(--font-body);display:flex;gap:7px;align-items:center;flex-wrap:wrap}
.msg .body{color:var(--ink);font-size:14.5px;margin-top:5px;white-space:pre-wrap;word-break:break-word;line-height:1.55}
.msg .when{font-size:11px;opacity:.5;margin-top:4px}
.msg.mine .t{color:var(--brand-bright)}
/* background was var(--navy-2) -- #081230, a fixed BRAND navy that has nothing
   to do with the palette. On any light theme the file card was a black slab in
   the middle of a white conversation, its size line dark-on-dark and its Open
   button invisible.

   It slipped past the literal-colour audit because it is a var(), and the audit
   only looked for hex. A theme-blind TOKEN is exactly as wrong as a theme-blind
   hex; the probe checks for both now. */
.msg .media{border:1px solid var(--line-2);border-radius:var(--r-sm);padding:12px;
  margin-top:8px;background:var(--raise);color:var(--ink)}
.msg .media .t{color:var(--ink)}
.msg .media .m{color:var(--muted)}
.msg.mine .media{background:color-mix(in srgb, var(--brand) 9%, var(--raise));
  border-color:color-mix(in srgb, var(--brand) 24%, var(--line-2))}
.rxrow{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}
.rx{background:var(--surface-2);border:1px solid var(--line);color:var(--muted);
  font:600 11.5px/1 var(--font-body);padding:6px 10px;border-radius:99px;cursor:pointer;
  transition:border-color .12s,color .12s}
.rx:hover{border-color:var(--brand);color:var(--ink)}
.rx.on{background:rgba(47,99,240,.18);border-color:var(--brand);color:var(--brand-bright)}
.msgtools{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px;opacity:0;transition:opacity .14s}
.msg:hover .msgtools,.msg:focus-within .msgtools{opacity:1}
@media(hover:none){.msgtools{opacity:.75}}
.typing{color:var(--muted);font-size:12.5px;font-style:italic;padding:10px 0 2px}

/* ---------- invite ---------- */
.codebox{background:var(--raise-2);border:1px solid var(--line-2);border-radius:var(--r-sm);
  padding:14px;margin-top:6px}
.codelabel{display:block;font:600 11px/1 var(--font-body);letter-spacing:.1em;
  text-transform:uppercase;color:var(--faint);margin-bottom:8px}
.codeval{font-family:var(--font-mono);font-size:13px;color:var(--brand-bright);
  word-break:break-all;line-height:1.5}
.codeval.big{font-size:24px;letter-spacing:.18em;color:var(--ink);text-align:center;
  font-weight:500;padding:4px 0}

/* ---------- Slack-style shell: rail + sidebar + pane ---------- */
.shell{display:grid;grid-template-columns:1fr;height:100dvh;overflow:hidden}
@media(min-width:860px){.shell{grid-template-columns:72px 260px 1fr}}
.rail{display:none;background:var(--rail);border-right:1px solid var(--line);
  flex-direction:column;align-items:center;padding:12px 0;gap:6px;overflow-y:auto}
@media(min-width:860px){.rail{display:flex}}
.rail .rb{width:46px;height:46px;border-radius:12px;border:1px solid transparent;background:transparent;
  color:var(--muted);display:grid;place-items:center;cursor:pointer;font:700 9px/1.15 var(--font-body);
  text-align:center;letter-spacing:.02em;padding:4px;transition:background .14s,color .14s}
.rail .rb:hover{background:var(--surface-2);color:var(--ink)}
.rail .rb.on{background:var(--brand);color:#fff;border-color:var(--brand)}
.rail .rb .dot{position:absolute;margin:-22px 0 0 26px;background:var(--signal);color:#fff;
  font:700 9px/1 var(--font-body);padding:3px 5px;border-radius:99px}

.side{display:none;background:var(--surface);border-right:1px solid var(--line);
  flex-direction:column;overflow:hidden}
@media(min-width:860px){.side{display:flex}}
.side .sh{padding:14px 16px;border-bottom:1px solid var(--line)}
.side .sh b{display:block;font:700 15px/1.3 var(--font-head);letter-spacing:-.02em}
.side .sh span{font-size:11.5px;color:var(--muted)}
.side .sl{flex:1;overflow-y:auto;padding:10px}
.side .grp{font:700 10.5px/1 var(--font-body);letter-spacing:.12em;text-transform:uppercase;
  color:var(--faint);padding:12px 8px 7px;display:flex;align-items:center;justify-content:space-between}
.side .grp button{background:none;border:0;color:var(--faint);cursor:pointer;font-size:16px;line-height:1;padding:0 4px}
.side .ch{display:flex;align-items:center;gap:8px;padding:8px 9px;border-radius:8px;cursor:pointer;
  color:var(--ink-2);font-size:13.5px;transition:background .12s}
.side .ch:hover{background:var(--surface-2)}
.side .ch.on{background:var(--brand);color:#fff}
.side .ch .nm{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.side .ch .ub{background:var(--signal);color:#fff;font:700 10px/1 var(--font-body);
  padding:3px 6px;border-radius:99px}
.side .ch.on .ub{background:rgba(255,255,255,.28)}
.presence{width:8px;height:8px;border-radius:50%;background:var(--faint);flex:0 0 auto}
.presence.on{background:var(--lime)}

.pane{display:flex;flex-direction:column;overflow:hidden;min-width:0}
.pane .ph{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid var(--line);
  background:var(--card);flex:0 0 auto;padding-top:calc(12px + env(safe-area-inset-top))}
@media(min-width:860px){.pane .ph{padding-top:12px}}
.pane .ph .pt{flex:1;min-width:0}
.pane .ph .pt b{display:block;font:700 15px/1.3 var(--font-head);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap;letter-spacing:-.02em}
.pane .ph .pt span{font-size:11.5px;color:var(--muted)}
.pane .pb{flex:1;overflow-y:auto;padding:16px;overscroll-behavior:contain}
.pane .pc{flex:0 0 auto;border-top:1px solid var(--line);background:var(--card);padding:12px 16px;
  padding-bottom:calc(12px + env(safe-area-inset-bottom))}
.pane .pc textarea{min-height:46px;max-height:180px;margin:0}

/* mobile: a drawer instead of a fixed sidebar */
.mobbar{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--line);
  background:var(--card);padding-top:calc(10px + env(safe-area-inset-top))}
@media(min-width:860px){.mobbar{display:none}}
.mobbar .burger{background:var(--surface-2);border:1px solid var(--line);color:var(--ink);
  width:38px;height:38px;border-radius:9px;font-size:16px;cursor:pointer;flex:0 0 auto}
.drawer{position:fixed;inset:0 auto 0 0;width:280px;max-width:86vw;background:var(--surface);
  border-right:1px solid var(--line);z-index:70;transform:translateX(-102%);
  transition:transform .2s ease;display:flex;flex-direction:column}
.drawer.on{transform:none}
.scrim{position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:65;display:none}
.scrim.on{display:block}
@media(min-width:860px){.drawer,.scrim{display:none!important}}

/* profile menu */
.avatar{width:34px;height:34px;border-radius:10px;background:var(--grad-brand);color:#fff;
  display:grid;place-items:center;font:700 13px/1 var(--font-head);cursor:pointer;flex:0 0 auto;border:0}
.pmenu{position:absolute;right:14px;top:56px;z-index:80;background:var(--card);
  border:1px solid var(--line-2);border-radius:var(--r);box-shadow:var(--shadow);
  min-width:260px;padding:14px;display:none}
.pmenu.on{display:block}
.pmenu .pe{font:700 13.5px/1.3 var(--font-body);word-break:break-all}
.pmenu .pr{font-size:11.5px;color:var(--muted);margin-top:3px}
.pmenu hr{border:0;border-top:1px solid var(--line);margin:12px 0}
.perm{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}
.perm span{font:600 10px/1 var(--font-mono);background:var(--surface-2);color:var(--ink-2);
  padding:5px 7px;border-radius:5px}

/* ---------- rail polish: icons, labels, active indicator ---------- */
.rail{width:72px;padding:14px 0 18px;gap:2px}
.rail .rb{position:relative;width:56px;height:54px;border-radius:12px;display:flex;
  flex-direction:column;align-items:center;justify-content:center;gap:3px;
  color:var(--faint);background:transparent;border:0;cursor:pointer;
  transition:color .15s,background .15s}
.rail .rb svg{width:21px;height:21px;flex:0 0 auto}
.rail .rb i{font:600 9px/1 var(--font-body);font-style:normal;letter-spacing:.02em;
  opacity:.9;max-width:56px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rail .rb:hover{color:var(--ink);background:var(--surface-2)}
.rail .rb.on{color:#fff;background:transparent}
.rail .rb.on::before{content:"";position:absolute;left:-14px;top:12px;bottom:12px;width:3px;
  border-radius:0 3px 3px 0;background:var(--brand-bright)}
.rail .rb.on svg{color:var(--brand-bright)}
.rail .rb.on i{color:var(--ink)}
.rail .rb .dot{position:absolute;top:6px;right:8px;margin:0;background:var(--signal);
  color:#fff;font:700 9px/1 var(--font-body);padding:3px 5px;border-radius:99px;
  box-shadow:0 0 0 2px var(--navy-2)}
.side .ch .ci{display:grid;place-items:center;width:18px;height:18px;flex:0 0 auto;opacity:.85}
.side .ch .ci svg{width:16px;height:16px}

/* ---------- chat polish: avatars, grouping, hover ---------- */
.msg{display:grid;grid-template-columns:36px 1fr;gap:11px;padding:9px 6px;border:0;border-radius:10px;
  transition:background .12s}
.msg:hover{background:var(--surface)}
.msg::before{content:attr(data-in);grid-row:1/span 5;width:36px;height:36px;border-radius:10px;
  background:var(--surface-2);color:var(--brand-bright);display:grid;place-items:center;
  font:700 12px/1 var(--font-head);border:1px solid var(--line)}
.msg.mine::before{background:var(--brand);color:#fff;border-color:var(--brand)}
.msg .t{font:600 13.5px/1.4 var(--font-body);gap:8px}
.msg .t .ts{font:400 11px/1 var(--font-body);color:var(--faint);margin-left:2px}
.msg .body{font-size:14.5px;line-height:1.55;margin-top:3px;color:var(--ink)}
.msg .when{display:none}
.msg .media{max-width:420px}
.msgtools{gap:5px}
.rx{padding:4px 9px;font-size:11px}

/* ---------- empty states with some character ---------- */
.empty{color:var(--muted);font-size:13.5px;text-align:center;padding:34px 14px;line-height:1.65}

/* ---------- scrollbars ---------- */
.pb::-webkit-scrollbar,.sl::-webkit-scrollbar{width:10px}
.pb::-webkit-scrollbar-thumb,.sl::-webkit-scrollbar-thumb{
  background:var(--line-2);border-radius:99px;border:3px solid transparent;background-clip:content-box}
.pb::-webkit-scrollbar-thumb:hover,.sl::-webkit-scrollbar-thumb:hover{background:var(--faint);background-clip:content-box}

/* ---------- focus visibility, for keyboard and for audits ---------- */
:focus-visible{outline:2px solid var(--brand-bright);outline-offset:2px;border-radius:6px}

/* ---------- modal (its own class: .scrim is drawer-only and hidden on desktop) ---------- */
.modal{position:fixed;inset:0;background:rgba(2,6,16,.66);z-index:90;
  display:grid;place-items:center;padding:16px;animation:fade .14s ease}
.mbox{width:min(480px,94vw);max-height:86vh;overflow:auto;margin:0;
  animation:pop .16s cubic-bezier(.2,.8,.3,1)}
@keyframes fade{from{opacity:0}to{opacity:1}}
@keyframes pop{from{opacity:0;transform:translateY(8px) scale(.985)}to{opacity:1;transform:none}}

/* ================= SLACK DENSITY PASS =================
   Slack is dense: 15px body, tight rows, almost no card chrome inside the pane.
   The card-per-section look belongs on a marketing page, not a workspace. */

.pane .pb{padding:0}
.pane .pb .card{background:transparent;border:0;box-shadow:none;border-radius:0;
  padding:14px 18px;margin:0;border-bottom:1px solid var(--line)}
.pane .pb .card:last-child{border-bottom:0}
.pane .pb .card h2{font:600 14px/1.35 var(--font-body);letter-spacing:0;margin:0 0 3px}
.pane .pb .card p.sub{font-size:12.5px;margin:0 0 11px;line-height:1.5}
.pane .pb .card.accent{background:rgba(47,99,240,.07);border-left:3px solid var(--brand)}

/* message rows: tight, hover-highlighted, grouped */
.pane .pb .msg{padding:6px 18px;border-radius:0;gap:9px;grid-template-columns:32px 1fr}
.pane .pb .msg::before{width:32px;height:32px;border-radius:7px;font-size:11px}
.pane .pb .msg:hover{background:rgba(255,255,255,.028)}
.msg .t{font-size:13px}
.msg .t .ts{font-size:10.5px}
.msg .body{font-size:14px;line-height:1.48;margin-top:1px}
.msgtools{margin-top:5px}
.rx{padding:3px 8px;font-size:10.5px;border-radius:6px}
.typing{padding:6px 18px;font-size:12px}

/* sidebar: Slack-tight rows */
.side .sh{padding:12px 15px}
.side .sh b{font-size:14.5px}
.side .sh span{font-size:11px}
.side .sl{padding:6px 8px}
.side .grp{padding:11px 8px 5px;font-size:10px}
.side .ch{padding:5px 8px;font-size:13.5px;border-radius:6px;gap:7px;min-height:28px}
.presence{width:7px;height:7px}

/* rail: slimmer */
.rail{width:66px}
.rail .rb{width:52px;height:50px;border-radius:9px}
.rail .rb svg{width:19px;height:19px}
.rail .rb i{font-size:8.5px}

/* pane header: one line, not a hero */
.pane .ph{padding:9px 18px}
.pane .ph .pt b{font:600 14.5px/1.3 var(--font-body);letter-spacing:0}
.pane .ph .pt span{font-size:11.5px}

/* composer: a box, not a card */
.pane .pc{padding:10px 18px 14px}
.pane .pc textarea{min-height:42px;font-size:14px;padding:10px 12px;border-radius:8px}

/* forms inside the pane: compact */
.pane .pb label{font-size:11.5px;margin:10px 0 5px}
.pane .pb input,.pane .pb select,.pane .pb textarea{padding:9px 11px;font-size:14px;border-radius:7px}
.pane .pb .item{padding:10px 12px;margin-bottom:6px;border-radius:7px}
.pane .pb .item .t{font-size:13.5px}
.pane .pb .item .m{font-size:12px;margin-top:3px}
.pane .pb button.btn{padding:9px 14px;font-size:13.5px;margin-top:11px;border-radius:7px}
.pane .pb button.btn.sm{padding:7px 11px;font-size:12.5px}
.pane .pb .note{font-size:12px;padding:10px 12px;border-radius:7px;margin-top:9px}
.pane .pb .kv{font-size:13px;padding:7px 0}
.pane .pb .empty{font-size:13px;padding:26px 16px}
.pane .pb .seclabel{font-size:10px;margin-bottom:9px}
.pane .pb .codeval{font-size:12px}
.pane .pb .codeval.big{font-size:20px;letter-spacing:.14em}
.pane .pb .tag{font-size:9.5px;padding:3px 6px}

/* the identity and workspace screens keep the roomier marketing feel */

/* Hidden hover-tools must not RESERVE space. opacity:0 still occupies the row,
   which is what put ~70px of dead air between every message. */
.pane .pb .msg{position:relative}
/* The toolbar was pinned top-right of the message. On YOUR OWN messages the
   content is right-aligned, so it landed straight on top of the name and the
   timestamp -- and on the file card under them. It now sits in whichever gutter
   the content is NOT using: left for your own, right for everyone else's.

   Solid rather than translucent, because a translucent chip over text is still
   two things in one place. */
.pane .pb .msgtools{position:absolute;top:2px;right:14px;margin:0;z-index:3;
  background:var(--card);border:1px solid var(--line-2);border-radius:8px;
  padding:3px;box-shadow:0 4px 14px rgba(0,0,0,.18);
  opacity:0;pointer-events:none;transition:opacity .12s}
.pane .pb .msg.mine .msgtools{right:auto;left:14px}
.pane .pb .msg:hover .msgtools,.pane .pb .msg:focus-within .msgtools{opacity:1;pointer-events:auto}
@media(hover:none){.pane .pb .msgtools{position:static;opacity:1;pointer-events:auto;
  background:transparent;border:0;box-shadow:none;padding:0;margin-top:5px}}
.pane .pb .rxrow:empty{display:none}
.pane .pb .rxrow{margin-top:5px}

/* Slack groups consecutive messages from the same person */
.pane .pb .msg + .msg{padding-top:3px}
.pane .pb .msg.grp::before{background:transparent;border-color:transparent;content:""}
.pane .pb .msg.grp{padding-top:1px;padding-bottom:1px}
.pane .pb .msg.grp .body{margin-top:0}

/* grid `gap` applies to ROWS too, so every internal element of a message was
   getting 9px of separation. Column gap only. */
.pane .pb .msg{column-gap:9px;row-gap:0}
.pane .pb .msg.grp::before{height:0}
.pane .pb .msg .body{margin-top:0}
.pane .pb .msg .t{margin-bottom:1px}

/* ---------------------------------------------------------------- tokens only
   Twelve rules above used to carry a literal navy -- #080e1a for the rail,
   #0a1120 for the sidebar, #111a2e for a ghost button's hover. With one dark
   theme that was merely lazy. With nine palettes it is a bug in eight of them,
   and the one that got reported was the worst kind: the ghost hover painted a
   near-black slab under dark ink, contrast 1.02, so the button's label simply
   disappeared when the pointer touched it.

   They are tokens now. probe-hardhex.mjs lists any literal colour left in a rule
   that is not scoped to a palette, and the ones it still reports are deliberate:
   a Google or Microsoft mark, white on a brand fill, and the half-light
   half-dark swatch that stands for "match my device". */

/* ================= site-v2 surface calm =================
   The heavy blue wash and the saturated blue panels were mine, not the brand.
   site-v2 stage 3 uses near-black navy surfaces with blue reserved for ACTION.
   Colour should mark what you can do, not decorate what you are reading. */
:root{
  --surface:#0b1220;
  --surface-2:#111a2e;
  --card:#0c1526;
  --line:#1a2438;
  --line-2:#243150;
  --ink:#e9eefb; --ink-2:#a3aec6; --muted:#6f7c99; --faint:#55617d;
}
body{
  /* no full-bleed gradient wash; a single quiet lift at the top only */
  background-image:radial-gradient(1100px 420px at 50% -18%, rgba(47,99,240,.10), transparent 70%);
}
.rail{background:var(--rail);border-right:1px solid var(--line)}
.side{background:var(--side)}
.pane .ph,.pane .pc{background:var(--chrome)}
.pane .pb{background:var(--pane)}

/* the active channel is a quiet selection, not a saturated blue slab */
.side .ch.on{background:rgba(47,99,240,.18);color:var(--ink);
  box-shadow:inset 2px 0 0 var(--brand-bright)}
.side .ch.on .ub{background:var(--brand)}

/* rail: active is an indicator, not a fill */
.rail .rb.on{background:rgba(47,99,240,.14)}

/* proof strip and cards on the landing page: flat, not glassy slabs */
.pf{background:var(--surface);border-color:var(--line)}
.card{box-shadow:none;border-color:var(--line);background:var(--card)}
.card.accent{background:linear-gradient(180deg,rgba(47,99,240,.08),transparent 60%),var(--card)}
#toast{background:var(--card);border:1px solid var(--line-2);color:var(--ink);
  box-shadow:0 10px 36px rgba(0,0,0,.28)}
.mark{border-radius:7px;display:block}
.logo .mark{width:28px;height:28px}

/* ================= blue is for ACTIONS, not panels =================
   The accent card was a tinted slab with a blue rule and a blue code box, which
   made whole sections read as "blue" even though nothing in them was clickable.
   site-v2 keeps surfaces near-black and spends colour only where you act. */
.card.accent,
.pane .pb .card.accent{
  background:var(--card);
  border:1px solid var(--line);
  border-left:1px solid var(--line);
  box-shadow:none;
}
/* a thin brand rule only where the section is genuinely the primary action */
.pane .pb .card.accent{border-left:2px solid var(--brand)}

.codebox{background:var(--raise-2);border:1px solid var(--line);color:var(--ink)}
.codeval{color:var(--ink-2)}
.codeval.big{color:var(--ink)}
.codelabel{color:var(--faint)}

/* notes: a quiet left rule, not a blue wash */
.note{background:var(--raise);border:1px solid var(--line);border-left:2px solid var(--line-2);
  color:var(--ink-2)}
.note b{color:var(--ink)}

/* tags sit quiet unless they carry a real warning */
.tag{background:var(--raise);color:var(--muted);border:1px solid var(--line)}
.tag.cap{background:var(--raise);color:var(--ink-2)}
.tag.intl{background:var(--raise);color:var(--ink-2)}

/* section headers in the pane */
.pane .pb .card h2{color:var(--ink)}
.pane .pb .card p.sub{color:var(--muted)}

/* secondary buttons stay outline; only the primary carries brand fill */
.pane .pb button.btn.ghost{border-color:var(--line-2);background:transparent}
.pane .pb button.btn.ghost:hover{background:var(--raise);border-color:var(--brand)}
button.btn{box-shadow:none}
button.btn:hover{background:var(--brand-strong)}


/* ==================================================================
   APPEARANCE SYSTEM
   Three independent axes, the way Google splits them, so a person can
   take the dark chrome without the blue, or the blue without the dark.

     data-theme    dark (default) | light | system  -> follows the OS
     data-accent   cobalt | violet | teal | amber | rose | slate
     data-density  comfortable (default) | compact

   Every surface below is a TOKEN. The first version of this hardcoded
   #0b0c0d and friends straight into .rail/.side/.pane, which made a
   light theme impossible without editing forty rules -- the same
   mistake as the navy tokens, one layer up.
   ================================================================== */
:root{
  --rail:#0b0c0d;      /* product rail, the darkest plane */
  --side:#16181b;      /* channel list */
  --pane:#121417;      /* the conversation itself */
  --chrome:#17191d;    /* headers, composer, cards */
  --input:#111214;
  --raise:#22252a;     /* hover, tags, chips */
  --raise-2:#30343c;   /* active row */
  --scrim-c:rgba(0,0,0,.55);
}

/* ---------- LIGHT ---------- */
:root[data-theme="light"]{
  --bg:#f7f8fa;
  --rail:#111316; --side:#f0f1f4; --pane:#ffffff; --chrome:#ffffff;
  --input:#ffffff; --raise:#e7e9ee; --raise-2:#d8dbe3;
  --surface:#f0f1f4; --surface-2:#e7e9ee; --card:#ffffff;
  --line:#dcdfe6; --line-2:#c3c8d2;
  --ink:#15171a; --ink-2:#3f444d; --muted:#666c78; --faint:#878e9a;
  --scrim-c:rgba(15,17,20,.35);
  --shadow:0 1px 2px rgba(16,20,28,.06), 0 8px 28px rgba(16,20,28,.08);
}
/* The rail stays dark in light mode, exactly as Slack and Gmail keep their
   navigation dark: it is chrome, not content, and a full-white rail loses
   the boundary that tells you where the app ends and the document begins. */
:root[data-theme="light"] .rail{color:#9aa1ad}
:root[data-theme="light"] .rail .rb:hover{
  /* Was background:#1e2126;color:#fff -- a near-black slab, written INSIDE
     the light theme. Pointing at a rail item turned it into a black brick
     in Daylight, Paper and Mint. It survived the literal-colour audit
     because that audit exempted anything scoped to a palette, which was
     too generous: a DARK literal inside the LIGHT theme is exactly the
     bug, and the exemption was hiding the clearest case of it. */
  background:color-mix(in srgb, var(--tone, var(--brand)) 11%, var(--raise));
  color:var(--ink)}
/* Removed: background:#2c3038 -- a near-black slab for the ACTIVE rail item,
   inside the light theme. A later rule already supersedes it, so it painted
   nothing, but leaving the defect in the source is how it comes back the
   next time somebody reorders the file. The tone rule below is the one. */

/* System = whatever the operating system is set to, re-evaluated live. */
@media (prefers-color-scheme: light){
  :root[data-theme="system"]{
    --bg:#f7f8fa;
    --rail:#111316; --side:#f0f1f4; --pane:#ffffff; --chrome:#ffffff;
    --input:#ffffff; --raise:#e7e9ee; --raise-2:#d8dbe3;
    --surface:#f0f1f4; --surface-2:#e7e9ee; --card:#ffffff;
    --line:#dcdfe6; --line-2:#c3c8d2;
    --ink:#15171a; --ink-2:#3f444d; --muted:#666c78; --faint:#878e9a;
    --scrim-c:rgba(15,17,20,.35);
    --shadow:0 1px 2px rgba(16,20,28,.06), 0 8px 28px rgba(16,20,28,.08);
  }
  :root[data-theme="system"] .rail{color:#9aa1ad}
  :root[data-theme="system"] .rail .rb:hover{
    background:color-mix(in srgb, var(--tone, var(--brand)) 11%, var(--raise));
    color:var(--ink)}
  :root[data-theme="system"] .rail .rb.on{background:#2c3038;color:#fff}
}

/* ---------- ACCENTS: the one colour the product spends ---------- */
:root,:root[data-accent="cobalt"]{
  --brand:#2f63f0; --brand-strong:#1740b0; --brand-bright:#7ea6ff;
  --grad-brand:linear-gradient(90deg,#2f63f0,#38a3ff);
}
:root[data-accent="violet"]{
  --brand:#7c4dff; --brand-strong:#5325cc; --brand-bright:#b9a0ff;
  --grad-brand:linear-gradient(90deg,#7c4dff,#a855f7);
}
:root[data-accent="teal"]{
  --brand:#0d9488; --brand-strong:#0a6f66; --brand-bright:#5eead4;
  --grad-brand:linear-gradient(90deg,#0d9488,#14b8a6);
}
:root[data-accent="amber"]{
  --brand:#c2760a; --brand-strong:#8f5606; --brand-bright:#fbbf24;
  --grad-brand:linear-gradient(90deg,#c2760a,#f59e0b);
}
:root[data-accent="rose"]{
  --brand:#e11d63; --brand-strong:#a8104a; --brand-bright:#fb7aa8;
  --grad-brand:linear-gradient(90deg,#e11d63,#f43f5e);
}
/* No accent at all. Some rooms genuinely want no colour in them. */
:root[data-accent="slate"]{
  --brand:#5b6472; --brand-strong:#414854; --brand-bright:#a7aebb;
  --grad-brand:linear-gradient(90deg,#5b6472,#78828f);
}

/* ---------- CHROME, drawn entirely from tokens ---------- */
body{background:var(--bg);background-image:none;color:var(--ink)}
.rail{background:var(--rail);border-right:1px solid var(--line)}
.rail .rb{color:var(--muted)}
.rail .rb:hover{background:color-mix(in srgb, var(--tone, var(--brand)) 12%, var(--raise));
  color:var(--ink)}
.rail .rb.on{background:var(--raise-2);color:#fff;border-color:transparent}
.side{background:var(--side);border-right:1px solid var(--line)}
.side .ch{color:var(--ink-2)}
.side .ch:hover{background:var(--raise)}
.side .ch.on{background:var(--raise-2);color:var(--ink);box-shadow:inset 2px 0 0 var(--brand)}
.pane .ph,.pane .pc{background:var(--chrome);border-color:var(--line)}
.pane .pb{background:var(--pane)}
.mobbar{background:var(--chrome)}
.drawer{background:var(--side)}
.scrim{background:var(--scrim-c)}

input,select,textarea{background:var(--input);border-color:var(--line);color:var(--ink)}
.codebox{background:var(--input);border:1px solid var(--line)}
.codeval{color:var(--ink-2)} .codeval.big{color:var(--ink)} .codelabel{color:var(--faint)}
.note{background:var(--surface);border:1px solid var(--line);
  border-left:2px solid var(--line-2);color:var(--ink-2)}
.note b{color:var(--ink)}
.tag,.tag.cap,.tag.intl{background:var(--raise);color:var(--ink-2)}
#toast{background:var(--raise);color:var(--ink)}
.pf{background:var(--surface);border-color:var(--line)}
.card{background:var(--card);border-color:var(--line);box-shadow:none}
.card.accent,.pane .pb .card.accent{background:var(--card);border:1px solid var(--line);
  border-left:2px solid var(--brand)}
.rx{background:var(--raise);border-color:var(--line);color:var(--muted)}
.rx.on{background:var(--raise-2);border-color:var(--line-2);color:var(--ink)}
.item{background:var(--surface)}
.item.pick:hover{background:var(--raise);border-color:var(--line-2)}
.msg .att{background:var(--input)}
.msg .body{color:var(--ink)}
nav button.on{background:var(--raise);border-color:var(--line-2)}
.feat code{background:var(--raise);color:var(--ink-2)}
/* 11px uppercase with wide tracking is SMALL text however important it looks,
   so it is held to 4.5 like everything else and uses --muted rather than the
   lighter --faint it drifted to. Measured at 2.53 before this. */
.seclabel{color:var(--muted);background:var(--surface);border-color:var(--line)}
button.btn{box-shadow:none}
button.btn:hover{background:var(--brand-strong)}
button.btn.ghost{color:var(--ink)}
button.btn.ghost:hover{background:var(--raise);border-color:var(--line-2)}
.mark,.rail .mark,.logo .mark{background:none;border-radius:7px;display:block}

/* ---------- DENSITY ---------- */
:root[data-density="compact"]{--pad:11px;--r:10px;--r-sm:7px}
:root[data-density="compact"] .side .ch{padding:4px 12px;font-size:13px}
:root[data-density="compact"] .pane .pb{padding:10px}
:root[data-density="compact"] .pane .ph,
:root[data-density="compact"] .pane .pc{padding:8px 12px}
:root[data-density="compact"] .msg{padding-top:2px;padding-bottom:2px}
:root[data-density="compact"] .msg .body{font-size:13.5px;line-height:1.45}
:root[data-density="compact"] .card{padding:12px 13px}
:root[data-density="compact"] .rail .rb{padding:6px 0}
:root[data-density="compact"] .rail .rb i{font-size:9.5px}

/* ---------- the sidebar header is the account button ---------- */
.side .sh,.drawer .sh{display:flex;align-items:center;gap:10px;cursor:pointer;
  transition:background .12s}
.side .sh:hover,.drawer .sh:hover{background:var(--raise)}
.sh .shw{flex:1;min-width:0}
.sh .shw b,.sh .shw span{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sh .shav{flex:0 0 auto;width:28px;height:28px;border-radius:8px;background:var(--grad-brand);
  color:#fff;font:700 11px/28px var(--font-body);text-align:center}

.pmenu{right:14px;top:56px;padding:12px;width:262px;background:var(--card);
  border:1px solid var(--line);max-height:calc(100vh - 90px);overflow-y:auto}
@media(min-width:860px){.pmenu{left:76px;right:auto;top:64px;width:272px}}
.pmenu .perm{display:flex;flex-wrap:wrap;gap:5px}
.pmenu .perm span{font:500 10.5px/1 var(--font-mono);background:var(--raise);
  color:var(--muted);padding:4px 7px;border-radius:5px}
.pmenu .btn.sm{font-size:13px;padding:9px 12px}
/* Signing out is not destructive -- .danger is orange, reserved for the kill-switch. */
.pmenu .signout{color:var(--ink-2);border-color:var(--line-2);background:transparent}
.pmenu .signout:hover{background:color-mix(in srgb,var(--t-rose) 14%,transparent);
  border-color:color-mix(in srgb,var(--t-rose) 42%,transparent);
  color:color-mix(in srgb,var(--t-rose) 72%,var(--ink))}

/* ---------- the appearance picker ---------- */
.approw{display:flex;gap:7px;margin-top:7px}
.appopt{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;
  padding:9px 6px;border:1px solid var(--line);border-radius:var(--r-sm);
  background:var(--surface);color:var(--ink-2);cursor:pointer;font:600 11.5px/1 var(--font-body);
  transition:border-color .12s,background .12s}
.appopt:hover{border-color:var(--line-2);background:var(--raise)}
.appopt.on{border-color:var(--brand);background:var(--raise);color:var(--ink);
  box-shadow:inset 0 0 0 1px var(--brand)}
.appopt .sw{width:100%;height:20px;border-radius:5px;border:1px solid var(--line)}
.swatches{display:flex;gap:9px;flex-wrap:wrap;margin-top:8px}
.swatch{width:29px;height:29px;border-radius:50%;cursor:pointer;border:2px solid transparent;
  outline:1px solid var(--line);transition:transform .1s;padding:0}
.swatch:hover{transform:scale(1.09)}
.swatch.on{border-color:var(--ink);outline-color:var(--brand)}

/* ---------- USERS: an admin table, not a list of cards ---------- */
.utable{padding:0;overflow-x:auto}
.utable table{width:100%;border-collapse:collapse;font-size:13.5px}
.utable thead th{text-align:left;padding:10px 12px;font:700 10.5px/1 var(--font-body);
  letter-spacing:.09em;text-transform:uppercase;color:var(--muted);
  border-bottom:1px solid var(--line);white-space:nowrap;background:var(--surface)}
.utable td{padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:middle}
.utable tbody tr:last-child td{border-bottom:0}
.utable tbody tr:hover{background:var(--raise)}
.utable tr.on{background:color-mix(in srgb, var(--brand) 12%, transparent)}
.utable .ck{width:38px}
.utable .ck input{width:15px;height:15px;accent-color:var(--brand);cursor:pointer;margin:0}
.utable .ckdash{color:var(--faint)}
.uname{font-weight:600;color:var(--ink)}
.uemail{font-size:12px;color:var(--muted);margin-top:1px}
.umuted{color:var(--muted);font-size:12.5px}
.uact{text-align:right;white-space:nowrap}
.usearch{width:200px;padding:7px 11px;font-size:13px;border-radius:var(--r-sm)}
.tag.warn{background:rgba(255,176,32,.16);color:var(--saffron)}

/* The bulk bar only exists while something is selected, so the page never shows
   a row of buttons that would refuse every click. */
.bulkbar{display:none;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:12px;
  padding:10px 13px;border:1px solid var(--brand);border-radius:var(--r);
  background:color-mix(in srgb, var(--brand) 10%, var(--card))}
.bulkbar.on{display:flex}
.bulkbar b{font-size:13px;margin-right:4px;color:var(--ink)}
.bulkbar .btn{margin:0}
@media(max-width:640px){
  .usearch{width:100%}
  /* Two columns used to be hidden here by POSITION -- 4 and 6, which were Type
     and Clearance when the table had seven. After the columns changed, 4 became
     Role and 6 became the row's action buttons, so the rule was hiding exactly
     the two things a phone most needs. Positional CSS outlives the layout it was
     written for; the card layout above makes hiding unnecessary anyway. */
}

/* ---------- install prompt ---------- */
#installbar{position:fixed;left:12px;right:12px;z-index:200;display:flex;align-items:center;gap:11px;
  padding:11px 12px;border:1px solid var(--line-2);border-radius:14px;background:var(--card);
  box-shadow:0 12px 40px rgba(0,0,0,.5);
  bottom:calc(12px + env(safe-area-inset-bottom));
  transform:translateY(140%);opacity:0;transition:transform .26s cubic-bezier(.2,.8,.2,1),opacity .26s;
  pointer-events:none}
#installbar.on{transform:translateY(0);opacity:1;pointer-events:auto}
#installbar img{border-radius:9px;flex:0 0 auto}
#installbar .it{flex:1;min-width:0;line-height:1.3}
#installbar .it b{display:block;font:700 14px/1.25 var(--font-head);color:var(--ink)}
#installbar .it span{display:block;font-size:12px;color:var(--muted);margin-top:2px}
#installbar .it span b{display:inline;font-size:12px;color:var(--ink-2)}
#installbar .btn{margin:0;flex:0 0 auto}
#installbar .ix{background:none;border:0;color:var(--muted);font-size:21px;line-height:1;
  padding:2px 5px;cursor:pointer;flex:0 0 auto}
#installbar .ix:hover{color:var(--ink)}
/* On a wide screen it sits bottom-right rather than spanning the window. */
@media(min-width:860px){#installbar{left:auto;right:18px;bottom:18px;width:348px}}

/* ---------- the account menu's "More" list ---------- */
.morelist{display:grid;grid-template-columns:1fr 1fr;gap:5px}
.mrow{display:flex;align-items:center;gap:7px;padding:8px 9px;border-radius:var(--r-sm);
  border:1px solid var(--line);background:var(--surface);color:var(--ink-2);cursor:pointer;
  font:600 12px/1 var(--font-body);text-align:left;transition:background .12s,border-color .12s}
.mrow:hover{background:var(--raise);border-color:var(--line-2);color:var(--ink)}
.mrow .ci{display:flex;flex:0 0 auto}
.mrow .ci svg{width:15px;height:15px}

/* ---------- iOS + Android ----------
   dvh, not vh: mobile Safari and Chrome both include the collapsing URL bar in
   100vh, so a full-height shell is taller than the window and the composer sits
   under the browser chrome until you scroll. */
html,body{height:100%}
.shell{height:100dvh}
@supports not (height:100dvh){.shell{height:100vh}}

/* A notch or a home indicator must not sit on top of the rail or the composer. */
.rail{padding-top:calc(10px + env(safe-area-inset-top));
  padding-bottom:calc(10px + env(safe-area-inset-bottom))}
.pane .pc{padding-bottom:calc(12px + env(safe-area-inset-bottom))}
.drawer{padding-top:env(safe-area-inset-top)}

@media (max-width:860px){
  /* iOS zooms the whole page when a focused input is under 16px, and never zooms
     back out. Every field the app focuses must stay at 16px on a phone. */
  input,select,textarea{font-size:16px}
  /* Apple's own minimum is 44px; below it a tap lands on the neighbour. */
  .side .ch,.drawer .ch,.mrow,.appopt{min-height:44px}
  .utable td{padding:12px}
  .utable .ck input{width:19px;height:19px}
  .swatch{width:34px;height:34px}
  #installbar .btn{min-height:40px}
}
/* Momentum scrolling, and stop a scroll at the end of the message list from
   dragging the whole page (rubber-banding over the composer on iOS). */
.pane .pb,.side .sl,.drawer .sl,.pmenu{-webkit-overflow-scrolling:touch;overscroll-behavior:contain}
/* A long email or code must wrap rather than widen the page on a 360px screen. */
.uemail,.codeval,.pmenu .pe{overflow-wrap:anywhere}
.uprof{width:auto;min-width:150px;padding:6px 9px;font-size:12.5px;border-radius:var(--r-sm)}

/* ---------- Users: sub-tabs, record page, permission editor ---------- */
.subtabs{display:flex;gap:4px;margin-bottom:12px;flex-wrap:wrap}
.stab{padding:7px 13px;border-radius:99px;border:1px solid var(--line);background:transparent;
  color:var(--muted);font:600 12.5px/1 var(--font-body);cursor:pointer;transition:background .12s}
.stab:hover{background:var(--raise);color:var(--ink)}
.stab.on{background:var(--raise-2);border-color:var(--line-2);color:var(--ink)}

/* 18px tall, which a finger cannot reliably hit on a product that ships as a
   phone app. The text stays the same size; the TARGET grows around it. */
.linkish{background:none;border:0;padding:3px 0;min-height:24px;display:inline-flex;
  align-items:center;font:600 13.5px/1.3 var(--font-body);
  color:var(--brand-bright);cursor:pointer;text-align:left}
.linkish:hover{text-decoration:underline}

.recav{width:52px;height:52px;border-radius:14px;background:var(--grad-brand);color:#fff;
  font:700 18px/52px var(--font-body);text-align:center;flex:0 0 auto}
.flds{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:12px 18px}
.fld span{display:block;font:600 10.5px/1 var(--font-body);letter-spacing:.09em;
  text-transform:uppercase;color:var(--muted);margin-bottom:5px}
.fld b{font-weight:600;font-size:13.5px;color:var(--ink)}
select.inline{width:auto;min-width:150px;padding:6px 9px;font-size:13px;border-radius:var(--r-sm)}

.permgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(215px,1fr));gap:10px;
  margin-top:12px;max-height:44vh;overflow-y:auto;padding-right:4px}
.permgrp{border:1px solid var(--line);border-radius:var(--r-sm);padding:9px 10px;background:var(--surface)}
.permhd{display:flex;align-items:center;justify-content:space-between;margin-bottom:7px}
.permhd b{font:700 11.5px/1 var(--font-body);letter-spacing:.05em;text-transform:uppercase;color:var(--ink-2)}
.permhd .btn{margin:0;padding:3px 8px;font-size:10.5px}
.permops{display:flex;flex-wrap:wrap;gap:5px 10px}
.permop{display:flex;align-items:center;gap:5px;font-size:12px;color:var(--ink-2);cursor:pointer}
.permop input{width:14px;height:14px;accent-color:var(--brand);margin:0;cursor:pointer}
/* A capability the author does not hold is shown disabled rather than hidden, so
   it is obvious WHY it cannot be given rather than simply absent. */
.permop.nope{opacity:.38;cursor:not-allowed}
.permop.nope input{cursor:not-allowed}
.mbox{max-width:760px}

/* ---------- mobile: the icons were sized for a desktop rail ---------- */
@media (max-width:860px){
  /* The drawer is a LIST, not a launcher. 24px glyphs at list size read as huge. */
  .drawer .ch .ci svg{width:16px;height:16px}
  .drawer .ch{padding:9px 14px;font-size:14px;min-height:42px}
  .drawer .grp{font-size:10.5px;padding:12px 14px 5px}
  .mobbar{padding:8px 12px;gap:9px}
  .mobbar .burger{width:34px;height:34px;font-size:17px;padding:0}
  .avatar{width:30px;height:30px;border-radius:9px;font-size:11px;line-height:30px}
  .sh .shav{width:26px;height:26px;line-height:26px;font-size:10px}
  .recav{width:44px;height:44px;border-radius:12px;font:700 15px/44px var(--font-body)}
  .msg .av{width:28px;height:28px;font-size:10.5px;line-height:28px}
  .stab{padding:6px 11px;font-size:12px}
}
/* The rail only appears above 860px, but keep its glyphs honest there too. */
.rail .rb svg{width:21px;height:21px}
.rail .rb i{font-size:9.5px;letter-spacing:.01em}

/* ---------- composer: one box, icons inside it ---------- */
.cbox{border:1px solid var(--line-2);border-radius:12px;background:var(--input);
  overflow:hidden;transition:border-color .12s}
.cbox:focus-within{border-color:var(--brand)}
.cbox textarea{border:0;background:transparent;border-radius:0;padding:11px 13px 4px;
  min-height:44px;resize:none}
.cbox textarea:focus{outline:0;box-shadow:none}
.ctools{display:flex;align-items:center;gap:8px;padding:5px 7px 7px 9px}
.chint{flex:1;min-width:0;font-size:11px;color:var(--faint);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.ibtn{width:32px;height:32px;flex:0 0 auto;display:flex;align-items:center;justify-content:center;
  border:0;border-radius:8px;background:transparent;color:var(--muted);cursor:pointer;
  font-size:17px;line-height:1;transition:background .12s,color .12s}
.ibtn:hover{background:var(--raise);color:var(--ink)}
.ibtn svg{width:18px;height:18px}
.ibtn.go{background:var(--brand);color:#fff}
/* AN INJECTED ICON IS SIZED WHEREVER IT LANDS.
 *
 * This said ".btn.has-ico svg" -- and the Settings nav (.setnavb) and the
 * density options (.appopt) are not .btn, so their icons matched no rule. An
 * <svg> with a viewBox and no width fills its container: the palette icon came
 * out around 400px tall and Settings was unusable (23 Sep, screenshot, and it
 * was already deployed). The size belongs on the class the decorator ADDS, so
 * it cannot miss a button whose other classes nobody predicted. */
button.has-ico > svg{width:15px;height:15px;flex:0 0 auto}
.btn.has-ico{display:inline-flex;align-items:center;justify-content:center;gap:7px}
.btn.sm.has-ico > svg{width:14px;height:14px}
/* The settings nav is a NAME with a note UNDER it. display:flex put the two
   side by side, so each item grew wide enough to run under the content pane
   and the whole screen read as overlapping text (23 Sep, screenshot). A grid
   keeps them stacked in one column with the icon spanning both rows -- which
   is what "beside both" has to mean. */
.setnavb.has-ico{display:grid;grid-template-columns:auto minmax(0,1fr);
  column-gap:9px;align-items:start}
.setnavb.has-ico > svg{grid-column:1;grid-row:1 / span 2;margin-top:2px;color:var(--muted)}
.setnavb.has-ico.on > svg{color:var(--brand)}
.setnavb.has-ico > b{grid-column:2;grid-row:1;min-width:0}
.setnavb.has-ico > i{grid-column:2;grid-row:2;min-width:0}
/* MEASURED, not guessed (probe-setnav.mjs, 23 Sep):
   desktop -- "Version and what this app does not do" is 190px of text in a
   152px column and the note carries white-space:nowrap, so it cannot wrap and
   pushed 225px of content into a 198px button. The note wraps now.
   phone -- the nav is a horizontal strip of chips, and the base rule gives
   .setnavb width:100%. As a grid that came out 563px wide PER ITEM on a 390px
   screen: 4199px of content, items starting at left=3906, and the nav running
   under the content pane. A chip is sized by its own words, so width:auto. */
.setnavb.has-ico > b{white-space:normal}
.setnavb.has-ico > i{white-space:normal}
@media (max-width:860px){
  .setnavb.has-ico{display:inline-flex;align-items:center;gap:7px;width:auto;flex:0 0 auto}
  .setnavb.has-ico > svg{grid-row:auto;margin-top:0}
  .setnavb.has-ico > b{white-space:nowrap}
  /* THE STRIP HAS TO BE THE THING THAT SCROLLS. .setnav already carried
     overflow-x:auto and was 1022px WIDE on a 390px phone, so its content never
     exceeded it and it never scrolled -- the overflow went up to .setwrap
     (visible) and .pb instead, which is a scrollbar in the wrong place at best.
     Measured with probe-setnav.mjs on 23 Sep. min-width:0 is what lets a flex
     child actually shrink below its content. */
  .setnav{max-width:100%;min-width:0;overflow-x:auto;scrollbar-width:none;
    -webkit-overflow-scrolling:touch;scroll-snap-type:x proximity}
  .setnav::-webkit-scrollbar{display:none}
  .setnavb.has-ico{scroll-snap-align:start}
}
/* The density options are a row of two; icon then word. */
.appopt.has-ico{display:inline-flex;align-items:center;justify-content:center;gap:7px}
.ibtn.go:hover{background:var(--brand-strong);color:#fff}
@media (max-width:860px){.ibtn{width:36px;height:36px}.chint{font-size:10.5px}}
.stab[disabled]{cursor:default;opacity:1}
.stab[disabled]:hover{background:var(--raise-2)}

/* ---------- document viewer: rendered pages ---------- */
.pgimg{display:block;width:100%;height:auto;border-radius:8px;user-select:none;-webkit-user-drag:none}
.pgtext{margin:0;white-space:pre-wrap;word-break:break-word;font:400 13px/1.55 var(--font-mono)}
/* The text layer exists only when COPY is granted; when it does, it sits under
   the image so selection works without hiding the page. */
.pgtext:not(.plain){position:absolute;inset:0;opacity:0;pointer-events:auto;overflow:hidden}
.page{position:relative}

/* A rendered page is the page. #page was styled as a sheet of text -- padding,
   pre-wrap and a 55vh white slab -- which wrapped the render in a blank sheet. */
/* A RENDERED PAGE IS NOT A SHEET OF TEXT. It inherited max-width:780px from
   .page and was drawn at 780px inside a 1440px viewport -- 660px unused, the
   image squeezed to 60.6% of its own pixels, which is why a wide table read as
   cut off (measured 23 Sep, probe-reader-layout.mjs). It takes the column it is
   given, and never more than its own pixels: upscaling is blur, not detail. */
.page.isimg{padding:0;min-height:0;white-space:normal;background:transparent;
  box-shadow:none;font-size:0;line-height:0;max-width:none;width:100%}
.page.isimg .pgimg{border-radius:var(--r-sm);box-shadow:0 10px 40px rgba(0,0,0,.45)}
.page.isimg .wm{z-index:2}

/* ---------- permission switches ---------- */
.permlist{margin-top:8px;display:flex;flex-direction:column;gap:7px}
.permlist .permrow{margin-bottom:0}
.permlist .pri{display:flex;align-items:center;justify-content:center;width:30px;height:30px;
  flex:0 0 auto;border-radius:8px;background:var(--raise);color:var(--ink-2)}
.permlist .pri svg{width:16px;height:16px}
.permlist input:checked ~ .pri{background:var(--brand);color:#fff}
.permlist .ptxt{display:flex;flex-direction:column;gap:2px;min-width:0}
.permlist .ptxt b{font:600 13px/1.3 var(--font-body);color:var(--ink)}
.permlist .ptxt small{font-size:11.5px;line-height:1.45;color:var(--muted)}
.permlist .permrow.nope{opacity:.5;cursor:not-allowed}
.permlist .permrow.nope input{cursor:not-allowed}
.permrow{display:flex;align-items:center;gap:10px;padding:10px 11px;border-radius:var(--r-sm);
  border:1px solid var(--line);background:var(--surface);cursor:pointer;margin-bottom:7px}
.permrow:hover{background:var(--raise);border-color:var(--line-2)}
.permrow input{width:16px;height:16px;accent-color:var(--brand);margin:2px 0 0;cursor:pointer;flex:0 0 auto}
.permrow span{display:block;min-width:0}
.permrow b{display:block;font:600 13.5px/1.35 var(--font-body);color:var(--ink)}
.permrow i{display:block;font-style:normal;font-size:12px;color:var(--muted);margin-top:2px}
.permrow input:disabled{cursor:not-allowed;opacity:.5}


/* ==================================================================
   PHONE: a table is not a table at 390px
   Six columns behind a horizontal scrollbar is a table nobody reads. Each row
   becomes a card and each cell announces itself, which is why every cell carries
   a data-label -- without the header above it, "partner" on its own means
   nothing. The markup stays a real <table>, so a screen reader and a wide screen
   both still get the grid.
   ================================================================== */
@media (max-width:720px){
  .utable{overflow-x:visible}
  .utable table,.utable tbody,.utable tr,.utable td{display:block;width:100%}
  .utable thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);
    white-space:nowrap}          /* hidden visually, still read aloud */
  .utable tr{border:1px solid var(--line);border-radius:var(--r-sm);margin:0 10px 9px;
    padding:10px 12px;background:var(--surface);position:relative}
  .utable tr:hover{background:var(--surface)}
  .utable tr.on{background:color-mix(in srgb, var(--brand) 12%, var(--surface))}
  .utable td{border:0;padding:4px 0;display:flex;align-items:center;gap:10px;min-height:0}
  .utable td[data-label]::before{
    content:attr(data-label);flex:0 0 40%;font:600 10.5px/1.3 var(--font-body);
    letter-spacing:.07em;text-transform:uppercase;color:var(--muted)}
  /* the tick box sits in the corner rather than in the flow */
  .utable td.ck{position:absolute;top:10px;right:10px;padding:0;width:auto}
  .utable td.ck::before{content:none}
  .utable td.uact{flex-wrap:wrap;gap:7px;padding-top:9px;margin-top:5px;
    border-top:1px solid var(--line)}
  .utable td.uact::before{content:none}
  .utable td.uact .btn{margin:0}
  .utable td:first-child+td{padding-right:34px}   /* clear of the tick box */
  .utable td .uemail{white-space:normal}
  .utable .empty{padding:20px 12px}
  .utable table thead th{display:none}
  .utable h2{padding:14px 12px 4px}

  /* Apple's minimum is 44px. Below it a tap lands on the neighbour, and this
     app measured a dozen controls under 40 on the Users screen alone. */
  #pane button.btn,#pane input,#pane select,#pane .stab{min-height:44px}
  #pane .btn.sm{padding:11px 14px;font-size:13px}
  #pane .stab{padding:11px 15px}
  .bulkbar{margin:0 10px 10px;position:sticky;bottom:8px;z-index:5;
    box-shadow:0 8px 28px rgba(0,0,0,.45)}
  .bulkbar .btn{flex:1 1 auto;min-width:96px}
  .flds{grid-template-columns:1fr}
  .permgrid{grid-template-columns:1fr;max-height:52vh}
  .morelist{grid-template-columns:1fr}
  .usearch{width:100%}
  .mbox{max-width:100%;border-radius:14px 14px 0 0}
}

/* Very narrow: stack the label above its value rather than squeezing both. */
@media (max-width:400px){
  .utable td[data-label]::before{flex-basis:34%;font-size:10px}
  .recav{width:40px;height:40px;line-height:40px;font-size:14px}
}

/* A 15px checkbox is a 15px tap target. On a phone it gets a 44px hit area
   through padding, while the box itself stays visually small. */
@media (max-width:720px){
  .utable .ck input,.selall,.selbox{width:22px;height:22px}
  .utable td.ck{padding:11px;margin:-11px}
  .permrow{min-height:48px;align-items:center}
  .permrow input{width:20px;height:20px;margin:0}
  .permop{min-height:40px}
  .permop input{width:18px;height:18px}
}


/* ---------- the reader: one column, scroll to the bottom ----------
   Prev/Next made a 244-page document a 244-click document. Pages stack, and each
   one renders as it comes near the viewport -- a view token lives 60 seconds, so
   rendering all 244 up front would spend it on pages nobody scrolled to. */
#stage{display:block;padding:18px 0 40px}
#pages{display:flex;flex-direction:column;align-items:center;gap:18px;width:100%}
.pgwrap{position:relative;width:100%;max-width:820px;padding:0 16px;flex:0 0 auto}
/* A page that is an IMAGE gets the room the screen has, up to the render's own
   width. Text pages keep the narrow measure -- a 1400px line of monospace is
   not readable, a 1400px scan is. */
.pgwrap:has(.pgimg){max-width:min(1600px, 100%)}
#pages{scroll-behavior:smooth}
/* Zoomed past fit, the page is wider than the column and the stage pans. */
#stage{overflow:auto;overscroll-behavior:contain}
/* One scrollbar: the document behind the fixed reader kept its own. */
body.reading{overflow:hidden}
.pgnum{position:absolute;top:-2px;left:-34px;font:600 11px/1 var(--font-mono);
  color:var(--faint);padding-top:6px}
@media (max-width:920px){.pgnum{left:auto;right:20px;top:-15px}}
.pgskel{min-height:62vh;border-radius:var(--r-sm);background:
  linear-gradient(100deg,var(--surface) 30%,var(--raise) 50%,var(--surface) 70%);
  background-size:220% 100%;animation:pgsk 1.4s linear infinite;
  display:flex;align-items:center;justify-content:center;color:var(--faint);font-size:12.5px}
@keyframes pgsk{to{background-position:-220% 0}}
.vbot .pg{flex:1;text-align:center}

/* ---------- install offer: across the top, where it is seen ---------- */
.topinstall{display:flex;align-items:center;gap:10px;padding:8px 14px;
  background:color-mix(in srgb, var(--brand) 14%, var(--chrome));
  border-bottom:1px solid var(--line-2);font-size:13px;color:var(--ink);
  padding-top:calc(8px + env(safe-area-inset-top))}
.topinstall img{border-radius:6px;flex:0 0 auto}
.topinstall span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.topinstall .btn{margin:0;flex:0 0 auto}
.topinstall .tix{background:none;border:0;color:var(--muted);font-size:19px;line-height:1;
  padding:2px 4px;cursor:pointer;flex:0 0 auto}
.topinstall .tix:hover{color:var(--ink)}
/* The shell sits below it, so the strip pushes rather than covers. */
body:has(.topinstall) .shell{height:calc(100dvh - 41px)}
@media (max-width:860px){
  .topinstall span{white-space:normal;font-size:12px;line-height:1.3}
  body:has(.topinstall) .shell{height:calc(100dvh - 56px)}
}

/* ---------- seats ---------- */
.seatbar{height:8px;border-radius:99px;background:var(--raise);overflow:hidden;margin-top:14px}
.seatfill{height:100%;background:var(--grad-brand);border-radius:99px;transition:width .3s}


/* ---------- chat: mine on the right, theirs on the left ----------

   Both halves of this were wrong before and both were found by MEASURING the
   rendered boxes, not by reading the stylesheet:

   1. The column swap never happened. `.pane .pb .msg` sets the columns at
      specificity 0,3,0 and `.msg.mine` was 0,2,0, so inside the chat pane -- the
      only place chat is -- the bare rule lost every time. The avatar was pushed
      into column 2, which was still the WIDE column, and my own words sat on the
      left under a stretched pill. Measured: grid-template-columns came back
      "32px 1031px" on my own message.

   2. `display:inline-block` does nothing here. `.body` is a grid ITEM, and grid
      items are blockified by the spec, so the bubble filled its column and a
      two-character reply rendered 763px wide. `width:fit-content` is what
      shrink-wraps a grid item; `justify-self` is what puts it on a side. */
.msg.mine,
.pane .pb .msg.mine{grid-template-columns:1fr 32px}
.msg.mine::before,
.pane .pb .msg.mine::before{grid-column:2;grid-row:1/span 5}
.msg.mine .t,.msg.mine .rxrow,.msg.mine .msgtools{justify-content:flex-end}

/* A bubble, so the side means something at a glance and a one-word reply is not
   a lonely line across the whole pane. The cap is a reading measure as well as a
   percentage: on a wide monitor 74% of the pane is still far too long a line. */
.msg .body{width:fit-content;max-width:min(74%,64ch);justify-self:start;
  padding:8px 12px;border-radius:14px;border-bottom-left-radius:5px;
  background:var(--raise);border:1px solid var(--line)}
.msg.mine .body{justify-self:end;
  border-radius:14px;border-bottom-right-radius:5px;
  background:color-mix(in srgb, var(--brand) 22%, var(--card));
  border-color:color-mix(in srgb, var(--brand) 36%, var(--line))}
.msg .media,.msg .att{justify-self:start;width:fit-content;max-width:min(74%,420px)}
.msg.mine .media,.msg.mine .att{justify-self:end}
/* A run of messages from the same person hides the avatar, and the bubble has to
   keep its place in the column or it jumps left under the previous one. */
.pane .pb .msg.grp .body{margin-top:2px}
@media (max-width:640px){.msg .body{max-width:88%}
  .msg .media,.msg .att{max-width:88%}}

/* ---------- permission-set editor: grouped switches ----------
   Twenty-eight checkboxes in one column is a wall nobody reads, and a wall
   nobody reads is how a permission gets ticked by accident. */
.fgrp{border:1px solid var(--line);border-radius:10px;margin:10px 0;overflow:hidden}
.fgrph{display:flex;align-items:center;gap:9px;padding:8px 11px;background:var(--raise);
  border-bottom:1px solid var(--line)}
.fgrph .seclabel{margin:0;flex:1}
.fgcount{font:600 11px/1 var(--font-mono,var(--font-body));color:var(--faint);
  background:var(--card);border:1px solid var(--line);border-radius:999px;padding:4px 8px}
.fgrp .permrow{border-top:1px solid var(--line);margin:0}
.fgrp .permrow:first-of-type{border-top:0}

/* The one profile that is above all of it should look like it. */
.tag.super,.chip.super{background:color-mix(in srgb,var(--saffron) 18%,transparent);
  color:var(--saffron);border:1px solid color-mix(in srgb,var(--saffron) 42%,transparent)}

/* ================================================================
   SEMANTIC COLOUR
   ================================================================
   Measured before writing any of this: on a single screen, ONE hue (220deg,
   blue) accounted for 195 of 209 coloured elements in dark and 489 of 502 in
   light. Everything in the product -- a status, a rank, a role, a file's
   classification, a permission group -- was painted the same colour, so none of
   them could be told apart without reading the words.

   The fix is not "more colours". It is colour that CARRIES something. Each tone
   below is attached to a meaning and used only for that meaning, so scanning a
   list of people or a wall of permissions tells you something before you read
   it. The surfaces stay neutral; the colour sits in the small marks.

   One token set, both themes, because a tone that means "restricted" must mean
   restricted in light and in dark. */
:root{
  --t-blue:#2f63f0;    /* the brand. action, and "internal" */
  --t-violet:#7c4dff;  /* rank above the ordinary: Super Admin, customer */
  --t-indigo:#4f46e5;  /* something this workspace defined itself */
  --t-teal:#0d9488;    /* administrative, but bounded */
  --t-green:#16a34a;   /* good: active, granted, working */
  --t-amber:#d97706;   /* attention: confidential, invited, external, expiring */
  --t-rose:#e11d48;    /* stop: restricted, offboarded, revoked, failed */
  --t-slate:#64748b;   /* deliberately unremarkable: member, other, none */
}

/* A tone is a wash, a readable foreground and a hairline, derived from one
   colour so adding a tone is one line rather than three hand-picked values.
   color-mix against --card rather than white keeps them legible in both themes
   without a second set of rules. */
.tone-blue  {--tone:var(--t-blue)}
.tone-violet{--tone:var(--t-violet)}
.tone-indigo{--tone:var(--t-indigo)}
.tone-teal  {--tone:var(--t-teal)}
.tone-green {--tone:var(--t-green)}
.tone-amber {--tone:var(--t-amber)}
.tone-rose  {--tone:var(--t-rose)}
.tone-slate {--tone:var(--t-slate)}

.tag[class*="tone-"]{
  background:color-mix(in srgb, var(--tone) 13%, transparent);
  color:color-mix(in srgb, var(--tone) 72%, var(--ink));
  border:1px solid color-mix(in srgb, var(--tone) 30%, transparent);
  font-weight:600;letter-spacing:.01em;text-transform:none;
}
:root[data-theme="light"] .tag[class*="tone-"]{
  color:color-mix(in srgb, var(--tone) 82%, #000);
  background:color-mix(in srgb, var(--tone) 10%, #fff);
}

/* Classification is the one the product is actually about, so it is the one
   place a tone is allowed to be a solid chip rather than a wash. */
.cls{display:inline-flex;align-items:center;gap:6px;font:700 10.5px/1 var(--font-body);
  letter-spacing:.09em;text-transform:uppercase;padding:5px 8px;border-radius:6px}
.cls::before{content:"";width:5px;height:5px;border-radius:50%;background:currentColor}
.cls-public      {--tone:var(--t-slate)}
.cls-internal    {--tone:var(--t-blue)}
.cls-confidential{--tone:var(--t-amber)}
.cls-restricted  {--tone:var(--t-rose)}
.cls{background:color-mix(in srgb, var(--tone) 15%, transparent);
  color:color-mix(in srgb, var(--tone) 75%, var(--ink));
  border:1px solid color-mix(in srgb, var(--tone) 32%, transparent)}

/* The permission-set editor: eight groups, eight tones, so 28 switches read as
   a map instead of a wall. The tone touches the header strip only -- colouring
   28 rows would be the original problem with extra steps. */
.fgrp[data-tone="blue"]  {--tone:var(--t-blue)}
.fgrp[data-tone="violet"]{--tone:var(--t-violet)}
.fgrp[data-tone="indigo"]{--tone:var(--t-indigo)}
.fgrp[data-tone="teal"]  {--tone:var(--t-teal)}
.fgrp[data-tone="green"] {--tone:var(--t-green)}
.fgrp[data-tone="amber"] {--tone:var(--t-amber)}
.fgrp[data-tone="rose"]  {--tone:var(--t-rose)}
.fgrp[data-tone="slate"] {--tone:var(--t-slate)}
.fgrp[data-tone]{border-color:color-mix(in srgb, var(--tone) 26%, var(--line))}
.fgrp[data-tone] .fgrph{
  background:color-mix(in srgb, var(--tone) 10%, var(--raise));
  border-bottom-color:color-mix(in srgb, var(--tone) 24%, var(--line));
  box-shadow:inset 3px 0 0 var(--tone)}
.fgrp[data-tone] .fgrph .seclabel{color:color-mix(in srgb, var(--tone) 70%, var(--ink))}


/* ---------------------------------------------------------------- rail colour
   The tone was being mixed into the LABEL: `color:color-mix(tone 72%, ink)`.
   Measured off real pixels, that gave 4.05-4.40 for Settings (amber) on the
   light rails and 2.29-2.32 for the selected item on the dark ones -- under the
   4.5 floor, and in the dark case badly so. Tinting text toward a hue always
   drags it toward the middle of the range, which is exactly where contrast dies.

   So the tone carries the INDICATOR and the wash, and the label is plain --ink.
   The colour still says which destination you are on, and the word stays as
   readable as body text, because it now IS body text. */

/* The rail: each destination keeps its own tone, so "where am I" is answered by
   colour before it is answered by reading a 10px label. */
.rail .rb:nth-of-type(1){--tone:var(--t-blue)}
.rail .rb:nth-of-type(2){--tone:var(--t-violet)}
.rail .rb:nth-of-type(3){--tone:var(--t-teal)}
.rail .rb:nth-of-type(4){--tone:var(--t-amber)}
.rail .rb.on{background:color-mix(in srgb, var(--tone, var(--brand)) 16%, var(--raise));
  color:var(--ink);font-weight:700;
  box-shadow:inset 2px 0 0 var(--tone, var(--brand))}
.rail .rb.on svg,.rail .rb.on .ic{color:inherit}

/* ================================================================
   LIGHT THEME — the rail was still black
   ================================================================
   With the theme set to light the whole app went white except the left rail and
   the channel sidebar, which stayed near-black and kept their dark-mode text
   colours. The active item's label was then dark grey on near-black: invisible,
   not merely ugly. It happened because the rail's colours were written as fixed
   hex values rather than tokens, so the theme had nothing to override. */
:root[data-theme="light"]{
  --rail:#f3f5f9; --side:#f8fafd; --pane:#ffffff; --chrome:#ffffff;
  --input:#ffffff; --raise:#f6f8fc;
  --surface:#f6f8fc; --surface-2:#eef2f9; --card:#ffffff;
  --line:#e5e9f2; --line-2:#d6ddec;
  --ink:#0a1733; --ink-2:#3f4a63; --muted:#69748c; --faint:#9aa3b6;
  /* The site-v2 shadow scale. In light, depth comes from shadow; borders alone
     make every panel look like a wireframe. */
  --sh-1:0 1px 2px rgba(16,24,45,.06),0 1px 3px rgba(16,24,45,.04);
  --sh-2:0 6px 18px -8px rgba(16,24,45,.14),0 2px 6px rgba(16,24,45,.06);
}
:root[data-theme="light"] body{background:var(--surface);background-image:none}
:root[data-theme="light"] .rail{background:var(--rail);border-right:1px solid var(--line)}
:root[data-theme="light"] .side{background:var(--side);border-right:1px solid var(--line)}
:root[data-theme="light"] .pane .ph,
:root[data-theme="light"] .pane .pc{background:var(--chrome);border-color:var(--line)}
:root[data-theme="light"] .pane .pb{background:var(--pane)}
:root[data-theme="light"] .rail .rb{color:var(--muted)}
:root[data-theme="light"] .card{background:var(--card);border-color:var(--line);box-shadow:var(--sh-1)}
:root[data-theme="light"] .card.accent{
  background:linear-gradient(180deg,color-mix(in srgb,var(--brand) 7%,#fff),#fff 62%);
  border-color:color-mix(in srgb,var(--brand) 22%,var(--line))}
:root[data-theme="light"] .msg .body{background:var(--surface-2);border-color:var(--line)}
:root[data-theme="light"] .msg.mine .body{
  background:color-mix(in srgb,var(--brand) 12%,#fff);
  border-color:color-mix(in srgb,var(--brand) 26%,var(--line))}
:root[data-theme="light"] .msg:hover{background:rgba(16,24,45,.035)}
:root[data-theme="light"] .pane .pb .msg:hover{background:rgba(16,24,45,.035)}
:root[data-theme="light"] .side .ch.on{
  background:color-mix(in srgb,var(--brand) 12%,#fff);color:var(--ink)}
:root[data-theme="light"] thead th{background:var(--surface-2);color:var(--muted)}
:root[data-theme="light"] input,
:root[data-theme="light"] textarea,
:root[data-theme="light"] select{background:#fff;border-color:var(--line-2);color:var(--ink)}
:root[data-theme="light"] .note{background:var(--surface-2);border-color:var(--line)}
:root[data-theme="light"] .mbox{box-shadow:var(--sh-2)}

/* The install strip was a full-width tinted band with a headline in it, which is
   a lot of the screen spent on something most people press once. Same offer,
   one line, and it keeps out of the way. */
.topinstall{padding:6px 14px;font-size:12.5px;gap:9px}
.topinstall img{width:18px;height:18px}
.topinstall .btn{padding:5px 12px;font-size:12px;min-height:0}
body:has(.topinstall) .shell{height:calc(100dvh - 33px)}
@media(max-width:860px){body:has(.topinstall) .shell{height:calc(100dvh - 46px)}}
:root[data-theme="light"] .topinstall{background:var(--surface-2);
  border-bottom:1px solid var(--line);color:var(--ink-2)}

/* The tag classes that were already in the markup, mapped onto the tone system
   rather than rewritten at every call site. A tag that says `restr` in one file
   and `tone-rose` in another would be two vocabularies for one idea. */
.tag.restr{--tone:var(--t-rose)}
.tag.conf {--tone:var(--t-amber)}
.tag.intl {--tone:var(--t-blue)}
.tag.ok   {--tone:var(--t-green)}
.tag.warn {--tone:var(--t-amber)}
.tag.dead {--tone:var(--t-rose)}
.tag.cap  {--tone:var(--t-slate)}
.tag.restr,.tag.conf,.tag.intl,.tag.ok,.tag.warn,.tag.dead,.tag.cap{
  background:color-mix(in srgb, var(--tone) 13%, transparent);
  color:color-mix(in srgb, var(--tone) 72%, var(--ink));
  border:1px solid color-mix(in srgb, var(--tone) 30%, transparent)}
:root[data-theme="light"] .tag.restr,:root[data-theme="light"] .tag.conf,
:root[data-theme="light"] .tag.intl,:root[data-theme="light"] .tag.ok,
:root[data-theme="light"] .tag.warn,:root[data-theme="light"] .tag.dead,
:root[data-theme="light"] .tag.cap{
  color:color-mix(in srgb, var(--tone) 82%, #000);
  background:color-mix(in srgb, var(--tone) 10%, #fff)}

/* Super Admin should look like what it is the moment you see a list of people. */
.tag.tone-violet{font-weight:700}

/* The active rail item was a near-black slab in the LIGHT theme with dark-grey
   text on it: `:root[data-theme="light"] .rail .rb.on{background:#2c3038}` is
   specificity 0,4,0 and the tone rule above is 0,3,0, so the tone never applied
   and the label was unreadable rather than merely wrong. Matched here instead of
   raising the other rule, because the light theme genuinely wants different
   values — a 18% wash that works on a near-black rail is invisible on a #f3f5f9
   one. Found by reading computed styles after a screenshot, not from the source. */
:root[data-theme="light"] .rail .rb.on,
:root[data-theme="light"] .rail .rb.on i{
  background:color-mix(in srgb, var(--tone, var(--brand)) 14%, #fff);
  color:var(--ink);font-weight:700;
  border-color:color-mix(in srgb, var(--tone, var(--brand)) 28%, transparent);
  box-shadow:inset 2px 0 0 var(--tone, var(--brand))}
:root[data-theme="light"] .rail .rb.on i{background:none;box-shadow:none;border:0}
:root[data-theme="light"] .rail .rb.on svg{color:inherit}
@media (prefers-color-scheme:light){
  :root[data-theme="system"] .rail .rb.on,
  :root[data-theme="system"] .rail .rb.on i{
    background:color-mix(in srgb, var(--tone, var(--brand)) 14%, #fff);
    color:var(--ink);font-weight:700}
  :root[data-theme="system"] .rail .rb.on i{background:none}
  :root[data-theme="system"] .rail .rb.on svg{color:inherit}
}
/* In dark the same rule has to beat line 637's flat `--raise-2` fill. */
:root[data-theme="dark"] .rail .rb.on{
  background:color-mix(in srgb, var(--tone, var(--brand)) 22%, var(--raise));
  color:var(--ink);font-weight:700;
  box-shadow:inset 2px 0 0 var(--tone, var(--brand))}
:root[data-theme="dark"] .rail .rb.on svg,
:root[data-theme="dark"] .rail .rb.on i{color:inherit}

/* The account button on the rail. Pushed to the bottom so the destinations stay
   together at the top and this sits where people already look for it. */
.railme{margin-top:auto;width:34px;height:34px;border-radius:10px;flex:0 0 auto;
  border:1px solid var(--line-2);background:var(--raise);color:var(--ink);
  font:700 12px/1 var(--font-body);letter-spacing:.02em;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  transition:border-color .12s,background .12s}
.railme:hover{border-color:var(--brand);background:color-mix(in srgb,var(--brand) 14%,var(--raise))}
:root[data-theme="light"] .railme{background:#fff;border-color:var(--line-2)}

/* The sidebar holds channels and direct messages. On every other tab it was a
   260px column of empty grey next to a table that wanted the room, which reads
   as a half-loaded screen rather than a layout. Collapsed rather than removed,
   so the grid keeps its three tracks and nothing below has to move. Safe only
   because the account button moved to the rail first. */
@media(min-width:860px){
  html[data-tab]:not([data-tab="chat"]) .shell{grid-template-columns:72px 0 1fr}
  html[data-tab]:not([data-tab="chat"]) .side{
    opacity:0;pointer-events:none;overflow:hidden;border-right:0;min-width:0}
}

/* ---------- theme gallery ----------
   A grid of miniatures. Three radio buttons could not answer "what does Forest
   look like" without switching to it and switching back. */
.thgroup{font:700 10px/1 var(--font-body);letter-spacing:.1em;text-transform:uppercase;
  color:var(--faint);margin:12px 0 6px}
.thgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:7px}
.thtile{padding:0;border:1.5px solid var(--line);border-radius:10px;background:transparent;
  cursor:pointer;overflow:hidden;display:block;text-align:left;
  transition:border-color .13s,transform .13s}
.thtile:hover{border-color:var(--line-2);transform:translateY(-1px)}
.thtile.on{border-color:var(--brand);box-shadow:0 0 0 2px color-mix(in srgb,var(--brand) 26%,transparent)}
.thprev{display:flex;height:40px;width:100%;overflow:hidden}
.thrail{width:16%;flex:0 0 auto}
.thside{width:26%;flex:0 0 auto}
.thbody{flex:1;position:relative;padding:6px 5px;display:flex;flex-direction:column;gap:3px}
.thbody i{display:block;height:3px;border-radius:2px;width:74%;opacity:.85}
.thbody b{position:absolute;right:5px;bottom:5px;width:14px;height:6px;border-radius:3px}
.thname{display:block;font:600 10.5px/1 var(--font-body);color:var(--ink-2);
  padding:5px 6px;background:var(--card);border-top:1px solid var(--line)}
.thtile.on .thname{color:var(--ink)}

/* "Match my device" is a mode rather than a palette, so it reads as a row. */
.thsys{display:flex;align-items:center;gap:10px;width:100%;text-align:left;cursor:pointer;
  padding:9px 10px;border:1.5px solid var(--line);border-radius:10px;background:var(--raise);
  color:var(--ink);transition:border-color .13s}
.thsys:hover{border-color:var(--line-2)}
.thsys.on{border-color:var(--brand);box-shadow:0 0 0 2px color-mix(in srgb,var(--brand) 26%,transparent)}
.thsysw{width:26px;height:26px;border-radius:8px;flex:0 0 auto;border:1px solid var(--line-2);
  background:linear-gradient(105deg,#f6f8fc 50%,#16181b 50%)}
.thsys b{display:block;font:600 12.5px/1.2 var(--font-body)}
.thsys i{display:block;font:400 11px/1.3 var(--font-body);color:var(--muted);font-style:normal;margin-top:2px}

/* The menu now holds a gallery, so it needs room and a scroll of its own. */
.pmenu{max-height:min(78vh,620px);overflow-y:auto;width:min(330px,92vw)}
/* ON A PHONE THE MENU IS A SHEET, not a dropdown pinned under a 54px bar.
   Measured at 390x844 (probe-mobile.mjs, 23 Sep): the absolutely-positioned
   menu resolved to 26px tall holding 1200px of content, and Sign out sat at
   y=1211 -- off screen and unreachable. position:fixed with an explicit top
   and bottom gives it a height that does not depend on what its containing
   block happens to be, and its own scroll. */
@media (max-width:860px){
  .pmenu{position:fixed;left:8px;right:8px;top:auto;bottom:8px;width:auto;
    max-height:min(76vh,640px);height:auto;overflow-y:auto;
    border-radius:16px;padding:14px;
    padding-bottom:calc(14px + env(safe-area-inset-bottom))}
  .pmenu .morelist{max-height:none}
}
/* The quick sign-out sits with the avatar; on the rail it sits above it. */
/* Selecting conversations to delete. The bar only exists in manage mode, and
   manage mode only exists for somebody who holds room:delete. */
.chman{display:flex;align-items:center;gap:7px;padding:6px 12px 2px;flex-wrap:wrap}
.chman .chall{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--ink-2);cursor:pointer;flex:1 1 auto}
.chman .chall input{width:16px;height:16px;accent-color:var(--brand);cursor:pointer}
.chman .btn.sm{padding:5px 10px;font-size:12px}
.chman .chmanbtn{margin-left:auto}
.ch.conv.picking{cursor:pointer}
.ch.conv .chsel{width:16px;height:16px;accent-color:var(--brand);flex:0 0 auto;margin-right:2px;cursor:pointer}
@media (max-width:860px){
  .chman{padding:8px 12px 4px}
  .ch.conv .chsel{width:20px;height:20px}
}
.signout-quick{flex:0 0 auto}
.mobbar .signout-quick{width:34px;height:34px}
.rail .signout-quick.railout{width:36px;height:36px;margin:0 auto 6px}
@media(min-width:860px){.mobbar .signout-quick{display:none}}
@media(max-width:860px){.rail .signout-quick.railout{display:none}}
@media(max-width:860px){.thgrid{grid-template-columns:repeat(3,1fr)}}

/* `auto` is not a colour, it is "whatever this theme uses", so it is marked
   rather than being a seventh dot people have to remember the meaning of. */
.swatch.auto{position:relative}
.swatch.auto::after{content:"A";position:absolute;inset:0;display:flex;align-items:center;
  justify-content:center;font:800 9px/1 var(--font-body);color:#fff;
  text-shadow:0 1px 2px rgba(0,0,0,.45)}

/* ---------- Settings ----------
   Two columns on a desktop, stacked on a phone: a list of sections on the left
   and one section at a time on the right, which is the shape every product with
   more than three settings converges on. */
.setwrap{display:grid;grid-template-columns:232px 1fr;gap:18px;padding:18px;
  align-items:start;max-width:1100px}
.setnav{display:flex;flex-direction:column;gap:4px;position:sticky;top:12px}
.setnavb{display:block;width:100%;text-align:left;cursor:pointer;
  padding:9px 11px;border-radius:9px;border:1px solid transparent;background:transparent;
  color:var(--ink-2);transition:background .12s,border-color .12s}
.setnavb:hover{background:var(--raise);border-color:var(--line)}
.setnavb.on{background:color-mix(in srgb,var(--brand) 12%,transparent);
  border-color:color-mix(in srgb,var(--brand) 30%,transparent);color:var(--ink);
  box-shadow:inset 2px 0 0 var(--brand)}
.setnavb b{display:block;font:600 13px/1.2 var(--font-body)}
.setnavb i{display:block;font:400 11px/1.3 var(--font-body);font-style:normal;
  color:var(--muted);margin-top:2px}
.setbody{display:flex;flex-direction:column;gap:14px;min-width:0}
.setbody .card{margin:0}

/* A label/value pair, used all over the account and workspace sections. */
.kv{display:flex;align-items:baseline;gap:12px;padding:8px 0;border-top:1px solid var(--line)}
.kv:first-of-type{border-top:0}
.kv>span{flex:0 0 42%;color:var(--muted);font-size:12.5px}
.kv>b{flex:1;min-width:0;font:600 13.5px/1.4 var(--font-body);word-break:break-word}
.kv code{font:600 13px/1 var(--font-mono,var(--font-body));background:var(--raise);
  border:1px solid var(--line);border-radius:6px;padding:4px 7px;letter-spacing:.08em}

ul.plain{margin:8px 0 0;padding-left:18px;color:var(--ink-2);font-size:13.5px;line-height:1.65}
ul.plain li{margin:4px 0}

/* Inside Settings the gallery has real room, so it is not squeezed to the
   width of a dropdown. */
.setbody .thgrid{grid-template-columns:repeat(4,1fr)}
@media(max-width:1000px){.setbody .thgrid{grid-template-columns:repeat(3,1fr)}}
@media(max-width:860px){
  .setwrap{grid-template-columns:1fr;padding:12px;gap:12px}
  .setnav{position:static;flex-direction:row;overflow-x:auto;gap:6px;
    padding-bottom:4px;-webkit-overflow-scrolling:touch}
  .setnavb{flex:0 0 auto;white-space:nowrap;border:1px solid var(--line)}
  .setnavb i{display:none}
  .setbody .thgrid{grid-template-columns:repeat(2,1fr)}
}

/* ================================================================
   THE 4.5 FLOOR, APPLIED WHERE TINTS ARE INVOLVED
   ================================================================
   Widening the polish audit from six hand-picked selectors to every element
   that owns text turned up five more failures, all of the same shape: a colour
   mixed toward a HUE sits in the middle of the range, and the middle of the
   range is where contrast dies. Measured, each one:

     "Theme, accent, density"  1.18   muted note on a brand-tinted nav item
     "Appearance"              3.52   the same item's label
     "Or read this code"       3.67   an 11px uppercase label
     a person's name           3.33   brand blue as a link on a tinted row
     "Active"                  4.19   a green tone tag

   The rule that comes out of it: when a background is tinted, the text on it
   must be mixed toward INK, not toward the tint. A tinted background and tinted
   text are the same decision made twice. */

/* Settings nav: the selected item has a brand wash, so its text stops being
   --ink-2/--muted and becomes ink proper. */
.setnavb.on b{color:var(--ink)}
.setnavb.on i{color:color-mix(in srgb, var(--ink) 78%, var(--muted))}

/* 11px uppercase with .12em tracking is small text, whatever weight it carries. */
.seclabel{color:color-mix(in srgb, var(--ink) 62%, var(--muted))}

/* A name in a table is a link, and a link has to clear the floor on the row's
   background rather than on the page's. --brand is tuned for a white fill
   behind a button, not for text on a tinted row. */
.linkish{color:color-mix(in srgb, var(--brand) 62%, var(--ink))}
:root[data-theme="dark"] .linkish{color:var(--brand-bright)}

/* Tone chips: 72% toward the hue was too far for green, teal and blue on a light
   surface. The wash still carries the meaning; the word carries the reading. */
.tag[class*="tone-"]{color:color-mix(in srgb, var(--tone) 42%, var(--ink))}
:root[data-theme="light"] .tag[class*="tone-"]{
  color:color-mix(in srgb, var(--tone) 55%, #000)}
.tag.restr,.tag.conf,.tag.intl,.tag.ok,.tag.warn,.tag.dead,.tag.cap{
  color:color-mix(in srgb, var(--tone) 42%, var(--ink))}
:root[data-theme="light"] .tag.restr,:root[data-theme="light"] .tag.conf,
:root[data-theme="light"] .tag.intl,:root[data-theme="light"] .tag.ok,
:root[data-theme="light"] .tag.warn,:root[data-theme="light"] .tag.dead,
:root[data-theme="light"] .tag.cap{color:color-mix(in srgb, var(--tone) 55%, #000)}

/* The classification chip, same reasoning. */
.cls{color:color-mix(in srgb, var(--tone) 48%, var(--ink))}
:root[data-theme="light"] .cls{color:color-mix(in srgb, var(--tone) 58%, #000)}

/* And the permission-group headers in the editor. */
.fgrp[data-tone] .fgrph .seclabel{color:color-mix(in srgb, var(--tone) 42%, var(--ink))}
:root[data-theme="light"] .fgrp[data-tone] .fgrph .seclabel{
  color:color-mix(in srgb, var(--tone) 55%, #000)}

/* `--faint` is for things that are genuinely secondary -- a timestamp beside a
   name, a placeholder. It had drifted onto two SMALL-CAPS LABELS that name a
   whole section, measured at 3.67 on white:

     .codelabel   "Or read this code aloud"   11px 600
     .thgroup     "Light" / "Dark"            10px 700

   Three edits to .seclabel did nothing to either, because neither is a
   .seclabel -- which is what happens when you fix what you assume rather than
   what a probe names. */
.codelabel,.thgroup{color:var(--muted)}

/* The last two under the floor, both measured on Graphite:
     .thsys i   "Light by day, dark at night"   4.39   muted on --raise
     .tag.ok    "intact"                        4.26   green mixed 42% toward ink
   A tone mixed 42% toward ink is fine on a white surface and not quite enough on
   a dark one, because --ink there is near-white and the hue drags it down. Dark
   gets a lighter mix; light keeps the darker one. */
:root[data-theme="dark"] .tag[class*="tone-"],
:root[data-theme="dark"] .tag.restr,:root[data-theme="dark"] .tag.conf,
:root[data-theme="dark"] .tag.intl,:root[data-theme="dark"] .tag.ok,
:root[data-theme="dark"] .tag.warn,:root[data-theme="dark"] .tag.dead,
:root[data-theme="dark"] .tag.cap{color:color-mix(in srgb, var(--tone) 26%, var(--ink))}
:root[data-theme="dark"] .cls{color:color-mix(in srgb, var(--tone) 30%, var(--ink))}
.thsys i{color:color-mix(in srgb, var(--ink) 34%, var(--muted))}

/* ================================================================
   MESSAGE ACTIONS — react, reply, edit, delete
   ================================================================
   Was a row of link-text reading `+up  +ok  reply  pin  delete`, which looks
   like debug output and puts two hardcoded words where a reaction picker
   belongs. Four icon buttons now, in a raised strip, the way every chat product
   this one gets compared to does it. */
.msgtools{display:flex;gap:2px;align-items:center}
.mtool{width:30px;height:30px;padding:0;border:0;border-radius:7px;cursor:pointer;
  background:transparent;color:var(--muted);position:relative;
  display:flex;align-items:center;justify-content:center;
  transition:background .12s,color .12s}
.mtool svg{width:17px;height:17px}
.mtool:hover{background:var(--raise-2);color:var(--ink)}
.mtool:focus-visible{outline:2px solid var(--brand);outline-offset:1px}
.mtool.danger:hover{background:color-mix(in srgb,var(--t-rose) 16%,transparent);
  color:color-mix(in srgb,var(--t-rose) 58%,var(--ink))}
/* The reply count rides on the icon rather than taking a sixth slot. */
.mtool .cnt{position:absolute;top:1px;right:1px;min-width:13px;height:13px;
  border-radius:99px;background:var(--brand);color:#fff;
  font:700 9px/13px var(--font-body);padding:0 3px}

/* ---------- reaction pills ----------
   `ok 1` in a grey box was a database row on screen. A pill with the emoji at
   reading size and the count beside it, tinted when it is yours, is what tells
   you at a glance whether you already reacted. */
.rxpill{display:inline-flex;align-items:center;gap:5px;cursor:pointer;
  padding:3px 9px 3px 7px;border-radius:99px;
  border:1px solid var(--line-2);background:var(--raise);color:var(--ink-2);
  font:600 11.5px/1 var(--font-body);min-height:26px;
  transition:background .12s,border-color .12s,transform .1s}
.rxpill:hover{border-color:var(--brand);transform:translateY(-1px)}
.rxpill .e{font-size:14px;line-height:1}
.rxpill .n{font-variant-numeric:tabular-nums}
.rxpill.mine{background:color-mix(in srgb,var(--brand) 14%,var(--raise));
  border-color:color-mix(in srgb,var(--brand) 42%,var(--line-2));
  color:color-mix(in srgb,var(--brand) 52%,var(--ink))}
:root[data-theme="light"] .rxpill.mine{color:color-mix(in srgb,var(--brand) 70%,#000)}

/* ---------- the picker ----------
   Six faces, not forty. A work chat does not need a search box to say "yes". */
.emojipop{position:fixed;z-index:120;display:flex;gap:2px;padding:5px;
  background:var(--card);border:1px solid var(--line-2);border-radius:99px;
  box-shadow:0 10px 30px rgba(0,0,0,.24)}
.emojipop button{width:34px;height:34px;border:0;border-radius:99px;cursor:pointer;
  background:transparent;font-size:19px;line-height:1;
  display:flex;align-items:center;justify-content:center;
  transition:background .12s,transform .1s}
.emojipop button:hover{background:var(--raise-2);transform:scale(1.18)}
.emojipop button:focus-visible{outline:2px solid var(--brand);outline-offset:1px}

/* ---------- editing in place ----------
   The bubble becomes the editor, so what you are changing stays where it was. */
.medit{width:100%;min-width:min(420px,60vw);resize:none;font:inherit;
  background:var(--input);color:var(--ink);border:1px solid var(--brand);
  border-radius:10px;padding:8px 10px}
.meditrow{display:flex;gap:7px;align-items:center;margin-top:7px;flex-wrap:wrap}
.meditrow .btn{margin:0;width:auto}
.meditrow .sub{font-size:11px}

/* The overflow menu behind the message strip's "..." */
.msgmore{position:fixed;z-index:120;min-width:190px;padding:5px;
  background:var(--card);border:1px solid var(--line-2);border-radius:10px;
  box-shadow:0 12px 34px rgba(0,0,0,.26)}
.msgmore button{display:flex;align-items:center;gap:9px;width:100%;text-align:left;
  padding:8px 9px;border:0;border-radius:7px;background:transparent;cursor:pointer;
  color:var(--ink);font:600 12.5px/1.2 var(--font-body)}
.msgmore button:hover{background:var(--raise-2)}
.msgmore button svg{width:16px;height:16px;flex:0 0 auto;color:var(--muted)}

/* A label on the brand fill uses the colour MEASURED to be legible on it, not a
   hardcoded white. White failed on five of the nine palettes -- including High
   contrast, whose whole purpose is contrast -- and on three of the six accents.
   Found as a hovered "Copy link" at 3.74 by the hover gate, which is the sort of
   thing no amount of looking at the light theme would ever surface. */
button.btn,button.btn:hover,.btn--primary{color:var(--on-brand,#fff)}
button.btn.ghost,button.btn.ghost:hover{color:var(--ink)}
button.btn.danger,button.btn.danger:hover{color:#fff}
.mtool .cnt{color:var(--on-brand,#fff)}
.rail .rb .dot{color:var(--on-brand,#fff)}
.side .ch .ub{color:var(--on-brand,#fff)}
.sh .shav{color:var(--on-brand,#fff)}
#toast.ok,#toast.bad{color:var(--ink)}

/* ---------- workspace branding ----------
   A customer's logo is whatever shape their designer made it: a square glyph, a
   wide wordmark, an SVG with its own padding. Fixed width/height squashes a
   wordmark into a smear, so the app gives it a BOX and fits it inside --
   object-fit:contain means it is never stretched and never cropped. */
.railmark{width:36px;height:36px;border-radius:9px;display:flex;align-items:center;
  justify-content:center;overflow:hidden;margin-bottom:12px;flex:0 0 auto;
  background:var(--raise);border:1px solid var(--line)}
.railmark img{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;display:block}

.brandrow{display:flex;gap:16px;align-items:flex-start;flex-wrap:wrap}
.brandprev{width:96px;height:96px;flex:0 0 auto;border-radius:14px;
  display:flex;align-items:center;justify-content:center;overflow:hidden;
  background:var(--raise);border:1px solid var(--line-2)}
.brandprev img{max-width:78%;max-height:78%;width:auto;height:auto;object-fit:contain;display:block}
.brandfields{flex:1;min-width:260px}
.brandfields label{margin-top:8px}
.brandfields label:first-child{margin-top:0}
@media(max-width:640px){.brandprev{width:72px;height:72px}}

/* ================================================================
   CHAT: RHYTHM, AND A HOVER THAT DOES NOT SPAN THE WINDOW
   ================================================================
   Measured before touching it, on a 1108px pane:

     hover band   left 0, width 1108, border-radius 0px
     toolbar      left 960 -- pinned to the PANE edge, not near the message
     gap above    0 between EVERY message, grouped or not

   So a new speaker was separated from the previous one by exactly as much as a
   continuation of the same speaker: none. That is the single biggest reason a
   chat reads as crude -- the eye has nothing to group on, so every message looks
   like one undifferentiated column. And a hover highlight that runs the full
   width of a 1108px pane is a band across the screen, not a row. */

/* A real gap between speakers, almost none within a run. This is the rhythm
   every mature chat uses and it costs two rules. */
.pane .pb .msg{padding:5px 14px;margin:0 8px;border-radius:10px;
  transition:background .12s}
.pane .pb .msg + .msg{margin-top:10px}
.pane .pb .msg.grp{margin-top:1px;padding-top:1px;padding-bottom:1px}

/* The hover is a ROW, inset from the edges and rounded, so it reads as "this
   message" rather than "this stripe of the window". */
.pane .pb .msg:hover{background:color-mix(in srgb,var(--ink) 4%,transparent)}
:root[data-theme="dark"] .pane .pb .msg:hover{
  background:color-mix(in srgb,var(--ink) 6%,transparent)}

/* The toolbar belongs to the message: raised, at its top-right, overlapping the
   row's own edge rather than floating at the far side of the pane. It no longer
   pushes anything, because it is absolute and the row reserves no space for it. */
.pane .pb .msgtools{top:-14px;right:10px;padding:2px;gap:0;border-radius:9px}
.pane .pb .msg.mine .msgtools{right:auto;left:10px}
.mtool{width:28px;height:28px}
.mtool svg{width:16px;height:16px}

/* ---------- phone ----------
   At 390px a 134px toolbar covers a third of the screen, and there is no hover
   on a touch screen to summon it anyway. The existing (hover:none) rule makes it
   static; this makes it fit, and gives the bubbles the width they need. */
@media(max-width:640px){
  .pane .pb .msg{padding:4px 10px;margin:0 4px}
  .pane .pb .msg + .msg{margin-top:8px}
  /* The grouped rule has to be repeated INSIDE the query: it and `+ .msg` are
     both 0,4,0, so whichever comes later wins, and the media block is later.
     Measured at 8px between grouped lines on a phone before this -- the run of
     one person's messages had lost the thing that makes it read as a run. */
  .pane .pb .msg.grp{margin-top:1px}
  .msg .body{max-width:min(88%,52ch)}
  .msg .media,.msg .att{max-width:88%}
  .mtool{width:34px;height:34px}          /* a finger, not a pointer */
  .mtool svg{width:18px;height:18px}
  .rxpill{min-height:30px;padding:4px 11px 4px 9px}
  .emojipop button{width:42px;height:42px;font-size:22px}
}
@media(hover:none){
  /* Static, so it never sits on top of the words it belongs to. */
  .pane .pb .msgtools{position:static;opacity:1;pointer-events:auto;
    background:transparent;border:0;box-shadow:none;padding:0;margin-top:6px;
    justify-content:flex-start}
  .pane .pb .msg.mine .msgtools{justify-content:flex-end}
}

/* A deleted message keeps its place so the conversation still makes sense, and
   says what happened rather than silently vanishing mid-thread. */
.msg.gone .gonebody{font-style:italic;color:var(--faint);background:transparent;
  border:1px dashed var(--line-2);padding:6px 11px}
.pinnedtag,.editedtag{margin-left:6px}

/* "A newer version is ready." Bottom-centre, out of the way of the composer and
   the rail, and dismissible -- an update notice that blocks the thing you came
   to do is worse than the stale bundle it is warning about. */
#updatebar{position:fixed;left:50%;bottom:18px;transform:translate(-50%,140%);z-index:300;
  display:flex;align-items:center;gap:11px;padding:9px 11px 9px 15px;
  background:var(--card);border:1px solid var(--line-2);border-radius:99px;
  box-shadow:0 14px 40px rgba(0,0,0,.26);opacity:0;pointer-events:none;
  transition:transform .3s cubic-bezier(.2,.8,.2,1),opacity .3s;
  font:600 13px/1 var(--font-body);color:var(--ink);max-width:calc(100vw - 24px)}
#updatebar.on{transform:translate(-50%,0);opacity:1;pointer-events:auto}
#updatebar .btn{margin:0;width:auto}
#updatebar .ux{background:none;border:0;color:var(--muted);font-size:19px;line-height:1;
  cursor:pointer;padding:2px 6px}
#updatebar .ux:hover{color:var(--ink)}
@media(max-width:640px){#updatebar{bottom:calc(14px + env(safe-area-inset-bottom));
  font-size:12.5px;padding:8px 9px 8px 13px}}

/* ---------------- the app's own dialog ----------------
   Replaces confirm()/prompt(), which drew the BROWSER's box with the site's
   address in it -- unstyleable, untranslatable, indistinguishable from a scam
   prompt, and suppressible by the browser, so a confirmation the code relies on
   might never appear.

   TOP MIDDLE on a wide screen: a decision about the thing you just clicked
   belongs near the top, not floating in the centre of a tall page. CENTRED on a
   phone, because a box pinned to the top of a small screen collides with the
   notch and the browser toolbar. */
.askwrap { position: fixed; inset: 0; z-index: 9000; display: flex;
  justify-content: center; align-items: flex-start; padding: 7vh 16px 16px; }
.askwrap .askscrim { position: absolute; inset: 0; background: rgba(2, 6, 16, .52); opacity: 0;
  transition: opacity .16s ease; }
.askwrap.on .askscrim { opacity: 1; }
.askwrap .askbox { position: relative; width: min(460px, 100%);
  background: var(--card); color: var(--ink);
  border: 1px solid var(--line); border-radius: 14px;
  box-shadow: 0 24px 60px rgba(2, 6, 16, .34);
  padding: 20px 20px 16px;
  transform: translateY(-8px) scale(.985); opacity: 0;
  transition: transform .17s cubic-bezier(.2, .8, .3, 1), opacity .17s ease; }
.askwrap.on .askbox { transform: none; opacity: 1; }
.askwrap .askhead { margin: 0 0 6px; font-size: 16px; font-weight: 650; line-height: 1.35; }
.askwrap .asktext { margin: 0; color: var(--muted); font-size: 13.5px; line-height: 1.5; }
.askwrap .askinput { width: 100%; margin-top: 12px; }
.askwrap .askrow { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }

/* A phone: centred, and wide enough to read. */
@media (max-width: 700px) {
  .askwrap { align-items: center; padding: 16px; }
  .askwrap .askbox { width: 100%; }
  /* Full-width buttons, confirm on top, so the thumb lands on the deliberate
     choice rather than on whichever button happens to be nearest. */
  .askwrap .askrow { flex-direction: column-reverse; }
  .askwrap .askrow .btn { width: 100%; }
}

/* Reduced motion: appear, do not animate. */
@media (prefers-reduced-motion: reduce) {
  .askwrap .askbox, .askwrap .askscrim { transition: none; }
}

/* A file the reader cannot turn into pages. */
.nopreview { padding: 40px 28px; text-align: center; }
.nopreview h3 { margin: 0 0 8px; font-size: 15px; font-weight: 650; color: var(--ink); }
.nopreview p { margin: 0 auto; max-width: 420px; color: var(--muted);
  font-size: 13.5px; line-height: 1.55; }

/* ---------------- conversation rows ----------------
   The list used to say "Engineering / 2 members . 7 messages" -- metadata about
   a room, not a conversation. You could not tell which one had something new in
   it, so you opened each in turn. Avatar, name, what was last said, when, and
   whether you have read it: the shape every messaging product converges on,
   because it is the one that answers the question you actually have. */
.side .ch.conv, .drawer .ch.conv {
  display: flex; align-items: center; gap: 10px;
  /* Measured off the screenshot: rows were ~78px with a blank band where the
     preview belonged. Dense enough that a dozen conversations fit without
     scrolling, which is the point of a list. */
  padding: 7px 10px; min-height: 56px;
}
.side .ch.conv .cav, .drawer .ch.conv .cav {
  position: relative; flex: 0 0 34px; width: 34px; height: 34px; border-radius: 10px;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--brand) 14%, transparent);
  color: var(--brand); font-size: 11.5px; font-weight: 700; letter-spacing: .3px;
}
/* The offline dot was a dark grey blob clipping the avatar's corner -- it read
   as an error badge, not as "not here right now". Offline is now the ABSENCE of
   a mark; only online draws one. */
.side .ch.conv .cav .presence, .drawer .ch.conv .cav .presence {
  position: absolute; right: -1px; bottom: -1px; width: 10px; height: 10px;
  border-radius: 50%; background: transparent; box-shadow: none;
}
.side .ch.conv .cav .presence.on, .drawer .ch.conv .cav .presence.on {
  background: #22c55e; box-shadow: 0 0 0 2px var(--card);
}
.side .ch.conv .cmid, .drawer .ch.conv .cmid { flex: 1 1 auto; min-width: 0; display: block; }
.side .ch.conv .crow1, .drawer .ch.conv .crow1,
.side .ch.conv .crow2, .drawer .ch.conv .crow2 {
  display: flex; align-items: center; gap: 8px; min-width: 0;
}
.side .ch.conv .nm, .drawer .ch.conv .nm {
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-weight: 600; font-size: 13.5px;
}
.side .ch.conv .cwhen, .drawer .ch.conv .cwhen {
  flex: 0 0 auto; font-size: 10.5px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.side .ch.conv .cprev, .drawer .ch.conv .cprev {
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: 12px; color: var(--muted);
}
.side .ch.conv .cprev i, .drawer .ch.conv .cprev i { opacity: .7; }
/* The unread pill sits with the preview, not the name: it belongs to "is there
   something here for me", which is the second line's question. */
.side .ch.conv .ub, .drawer .ch.conv .ub { flex: 0 0 auto; }
/* Read it, and the row stops shouting. */
.side .ch.conv:has(.ub) .nm, .drawer .ch.conv:has(.ub) .nm { font-weight: 700; }
.side .ch.conv:has(.ub) .cprev, .drawer .ch.conv:has(.ub) .cprev { color: var(--ink); }

/* ---------------- settings, grouped ----------------
   Yours above, the workspace's below, because the difference that matters is
   who a change lands on. */
.setnav .setgroup {
  padding: 14px 12px 6px; font-size: 10.5px; font-weight: 700;
  letter-spacing: .6px; text-transform: uppercase; color: var(--muted);
}
.setnav .setgroup:first-child { padding-top: 4px; }

/* The people picker at upload. */
.pickbox {
  max-height: 220px; overflow: auto; border: 1px solid var(--line);
  border-radius: 10px; padding: 6px;
}
.pickbox .ck, .ck { display: flex; gap: 9px; align-items: flex-start;
  padding: 7px 8px; border-radius: 8px; font-size: 13.5px; }
.pickbox .ck:hover { background: color-mix(in srgb, var(--ink) 5%, transparent); }
.ck input[type="checkbox"] { width: auto; margin: 2px 0 0; flex: 0 0 auto; }
.ck .sub { display: block; font-style: normal; font-size: 11.5px; color: var(--muted); }

/* Audio and video in the reader. Sized to the page column so a portrait phone
   clip does not take over the screen, and a voice note does not sit alone on a
   page-sized white sheet. */
.pgmedia { display: block; max-width: 100%; margin: 0 auto; border-radius: 10px; background: #000; }
audio.pgmedia { height: 54px; background: transparent; margin: 28px auto; max-width: 520px; }
video.pgmedia { max-height: 72vh; }

/* "decrypting..." while the room key is fetched. */
.side .ch.conv .cprev .cload, .drawer .ch.conv .cprev .cload { opacity: .55; }

/* The rail label was clipping "AI WorkMate" to "AI". Two short lines beat one
   truncated one -- the label is what tells somebody what the tab IS. */
.rail .rb span, .rail .rb .rl {
  font-size: 10px; line-height: 1.15; text-align: center;
  white-space: normal; overflow-wrap: anywhere; max-width: 100%;
}

/* The skeleton's explanation, for the seconds a conversion takes. */
.pgskel small { display: block; max-width: 380px; margin: 10px auto 0;
  font-size: 12px; line-height: 1.5; opacity: .7; }
/* The waiting card is READ, not glanced at: a person stares at it for the whole
   conversion. Stack the lines, and let the first one be the one they see. */
.pgskel{flex-direction:column;text-align:center;padding:24px;color:var(--ink-2);font-size:13px}
.pgskel b{color:var(--ink);font-size:15px;font-weight:600}
.pgskel .elapsed{margin-top:10px;font:600 12px/1 var(--font-mono);color:var(--muted);letter-spacing:.04em}
.pgskel small{color:var(--muted);opacity:1}
/* display:block above would otherwise beat the hidden attribute, and the
   "still working" line showed at 0 s. Measured in shots/reader-waiting.png. */
.pgskel small[hidden]{display:none}
.pgskel .bar{width:min(360px,80%);height:6px;border-radius:4px;background:var(--raise);margin-top:12px;overflow:hidden}
.pgskel .bar>i{display:block;height:100%;width:0;background:var(--brand);transition:width .6s ease}
.pgskel .pct{margin-top:6px;font:600 12px/1 var(--font-mono);color:var(--ink-2);letter-spacing:.04em}
.pgskel .stage{font-size:12px;color:var(--muted);margin-top:4px}
/* The capability strip scrolled sideways with its scrollbar hidden: at phone width
   the sixth chip was simply not there (23 Sep, shots/phone-04-reader.png). Wrap. */
@media (max-width:640px){
  .vcaps{flex-wrap:wrap;overflow:visible;row-gap:6px}
  .vcaps .btn{margin-left:0!important;flex-basis:100%}
}}
/* A staged attachment sits at the top of the composer until Send. */
/* A staged attachment: what it is, how sensitive, and what the people
   receiving it may do. Two native <select> boxes were the wrong control --
   they hide the options until pressed and draw the operating system's own
   list over the composer (23 Sep). */
.attach{display:flex;flex-direction:column;gap:9px;padding:10px 11px 9px;
  border-bottom:1px solid var(--line-2);font-size:12.5px;color:var(--ink-2)}
.attach .ahead{display:flex;align-items:center;gap:8px;min-width:0}
.attach .aicon{display:flex;align-items:center;color:var(--muted);flex:0 0 auto}
.attach .aicon svg{width:16px;height:16px}
.attach .aname{font-weight:600;color:var(--ink);flex:1 1 auto;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.attach .asize{color:var(--muted);flex:0 0 auto;font-variant-numeric:tabular-nums}
.attach .ibtn{width:26px;height:26px;flex:0 0 auto}
.attach .arow{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.attach .alab{flex:0 0 68px;font-size:11px;letter-spacing:.03em;text-transform:uppercase;color:var(--faint)}
.attach small{color:var(--muted);font-size:11px}

/* Sensitivity: three mutually exclusive choices, all visible. */
.asegs{display:inline-flex;border:1px solid var(--line-2);border-radius:9px;overflow:hidden;background:var(--input)}
.aseg{border:0;background:transparent;color:var(--ink-2);font:600 12px/1 var(--font-body);
  padding:7px 11px;cursor:pointer;white-space:nowrap}
.aseg + .aseg{border-left:1px solid var(--line-2)}
.aseg:hover{background:var(--raise);color:var(--ink)}
.aseg.on{background:var(--brand);color:#fff}

/* Capabilities: five independent toggles whose state is visible at rest. */
.acaps{display:flex;flex-wrap:wrap;gap:6px;flex:1 1 auto}
.capchip{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line-2);
  background:var(--input);color:var(--muted);border-radius:99px;padding:6px 11px 6px 9px;
  font:600 12px/1 var(--font-body);cursor:pointer}
.capchip svg{width:14px;height:14px;flex:0 0 auto}
.capchip:hover:not([disabled]){border-color:var(--brand);color:var(--ink)}
.capchip.on{background:var(--brand);border-color:var(--brand);color:#fff}
.capchip[disabled]{opacity:.4;cursor:not-allowed}
@media (max-width:640px){
  .attach .alab{flex-basis:100%}
  .capchip{padding:8px 12px 8px 10px}
  .aseg{padding:9px 12px}
}
