/* shell.css — the three-pane Workspace shell + chat surfaces (P4).
   Ported VERBATIM from the gallery (docs/design/gallery.html, the visual LAW):
   the server rail, channel column, main chat pane, members rail, composer,
   thread pane, and the workspace's own empty/loading/edge states.

   Scope discipline (CLAUDE.md — define a thing once):
   - Anything primitives.css already owns (.btn/.iconbtn/.field/.av/.pres/.tag/
     .menu/.modal/.seg/.bar/.nav/.toast …) is REUSED, never redefined here.
   - `.screen` layout lives here only; base.css centres the P0 placeholder's
     `.ph` with margin:auto so it still reads inside this flex pane.
   - Colours come from tokens; the rgba() scrims on the server cover/name-bar and
     the media play-over are ported verbatim from the gallery (legibility scrims,
     not member colour — the one place the design language uses translucency).
   Beta is web-only (CANON §C.2): the shell fills the viewport and flexes down to
   ~1024px; it does NOT collapse to mobile tabs (that's a dormant post-beta gallery). */

/* ── shell frame ─────────────────────────────────────────────────────────── */
.app{display:flex;height:100%;overflow:hidden}
.rail{flex:none;width:var(--rail);background:var(--railbg);display:flex;flex-direction:column;align-items:center;gap:var(--s2);padding:var(--s2) 0;overflow-y:auto}
.railbtn{position:relative;width:38px;height:38px;flex:none;display:grid;place-items:center;border-radius:var(--r);background:var(--surface);color:var(--soft);font-weight:700;font-size:var(--fs-sm);transition:background var(--t),color var(--t)}
.railbtn:hover{background:var(--plate)}
/* the e! logo mark. The PNG is white with ~10% internal padding, so scale it up to fill the
   button and centre it. Base (:root = light theme) inverts it to a dark mark; dark mode keeps
   it white — so it reads on the rail in both themes whatever the file's background. */
/* the e! logo mark, painted with the PNG as an alpha MASK filled by a token colour — so it
   matches the rail like the other icons (no baked black/white tile, no invert), adapts to the
   theme automatically, and can't be clipped. Sized a touch under the button so it has air. */
.railbtn.home,.railbtn.home.on{background:none}   /* the logo never becomes a solid inked tile */
.railbtn.home:hover{background:var(--surface);color:var(--ink)}
/* owner 2026-09-01: the logo is a plain wordmark now (Feed, the route it used to open, is cut) —
   it's a <span>, not a <button>, so it never carries .on; this rule is dormant, kept only in case
   the mark ever needs an inked-active state again. */
.railbtn.home.on .railogo{background:var(--ink)}
/* eski_mark.png is the white e! on a TRANSPARENT background (derived from eski_logo.png). Use
   it as an alpha mask filled with `currentColor`, so the mark takes the button's own colour —
   --soft on the rail (matching the other icons), --on-ink when the button is active/inked, and
   --ink on hover. No tile, no invert, correct in both themes and the active state for free.
   Sized under the button so it isn't clipped. */
.railbtn.home .railogo{display:block;width:24px;height:24px;background:currentColor;
  -webkit-mask:url(/eski_mark.png) center/contain no-repeat;mask:url(/eski_mark.png) center/contain no-repeat}
/* server icon fills the square badge (radius --r, never round — round is avatars/dots only). */
.railbtn .railimg{width:100%;height:100%;object-fit:cover;border-radius:var(--r)}
.railbtn.on{background:var(--ink);color:var(--on-ink)}
/* B22 — active-rail marker: a left-edge pill so the SELECTED server/section is unmistakable even
   when the badge shows an uploaded icon (the ink fill is then hidden behind the image). The button
   sits ~10px in from the rail edge, so a small negative-left pill stays inside the rail's box. */
.railbtn.on::before{content:"";position:absolute;left:-8px;top:50%;transform:translateY(-50%);
  width:3px;height:20px;border-radius:0 2px 2px 0;background:var(--ink)}
.railbtn.home.on::before,.railbtn.user.on::before{display:none}   /* Feed logo + avatar have their own active treatment */
.railbtn .ct{position:absolute;top:-2px;right:-2px;min-width:15px;height:15px;padding:0 4px;background:var(--ink);color:var(--on-ink);border-radius:var(--r);font-size:9px;font-weight:700;display:grid;place-items:center}
.railbtn.on .ct{background:var(--paper);color:var(--ink)}
.railbtn .rdot{position:absolute;top:-1px;right:-1px;width:9px;height:9px;border-radius:50%;background:var(--ink);box-shadow:0 0 0 2px var(--railbg)}
/* owner 2026-09-01: servers deprioritized "for now" (file-explorer focus) — dimmed, not disabled;
   full opacity + colour on hover so it never reads as broken, just quiet at rest. */
.railbtn.dimmed{opacity:.4}
.railbtn.dimmed:hover{opacity:1}
.railsep{width:22px;height:1px;background:var(--line);flex:none;margin:1px 0}
/* the profile button is a PERSON, not a server: round, and anchored to the
   bottom of the rail (away from the square server badges) so it never reads as
   another server icon. */
.railbtn.user{margin-top:auto;border-radius:50%;background:none;padding:0;overflow:hidden}
.railbtn.user:hover{background:none}
.railbtn.user .pfp{width:38px;height:38px;border-radius:50%;background:linear-gradient(135deg,#8f8f8f,#565656);color:#fff;display:grid;place-items:center;font-weight:700;font-size:var(--fs-sm);transition:box-shadow var(--t);overflow:hidden}
.railbtn.user .pfp .pfpimg{width:100%;height:100%;object-fit:cover;border-radius:50%}
.railbtn.user:hover .pfp{box-shadow:0 0 0 2px var(--line2)}
.railbtn.user.on{background:none}
.railbtn.user.on .pfp{box-shadow:0 0 0 2px var(--railbg),0 0 0 4px var(--ink)}
.stage{flex:1;min-width:0;display:flex;overflow:hidden}
.screen{flex:1;min-width:0;display:flex;overflow:hidden}
.screen[hidden]{display:none}
/* a full-screen screen (auth / placeholder) sits straight in #stage, which is a
   plain block — give it the height so its centred content (margin:auto) lands. */
#stage>.screen{height:100%}

/* sign-in (P4.10 minimal magic-link; the full auth screen is P9) */
.authcard{margin:auto;width:320px;max-width:90%;display:flex;flex-direction:column;gap:var(--s3);text-align:center}
.authcard .wm{font-weight:700;font-size:var(--fs-xl)}
.authcard .sub{color:var(--muted);font-size:var(--fs-sm);margin:0}
.authcard .field{text-align:left}
.authcard .btn{justify-content:center}
.authnote{font-size:var(--fs-sm);color:var(--soft);margin:0}
.authnote.err{color:var(--danger)}
.authnote[hidden]{display:none}
/* Continue with Google — a full-width outline button; the brand G keeps its own
   colours (auth-logo exception), the label follows the theme. Slightly taller than
   a default .btn so the primary sign-in path reads as the anchor. */
.authcard .oauthbtn{gap:9px;padding:9px 12px;font-size:var(--fs-sm)}
.authcard .oauthbtn .gmark{flex:none}
/* the "or" separator between Google and the email fallback — a centred label on a
   hairline, no box (background-step/​hairline rules, eski-style §3) */
.ordiv{display:flex;align-items:center;gap:var(--s3);color:var(--muted);font-size:var(--fs-xs)}
.ordiv::before,.ordiv::after{content:"";flex:1;height:1px;background:var(--line)}

/* ── channel column ──────────────────────────────────────────────────────── */
.chan{flex:none;width:var(--chan);background:var(--surface);display:flex;flex-direction:column;overflow:hidden}
.chanhd{flex:none;height:48px;display:flex;align-items:center;justify-content:space-between;padding:0 var(--s3);background:var(--plate)}
.chanhd b{font-size:var(--fs-sm);font-weight:600}
/* server header: cover + square icon get real presence at the top of the column,
   not just the 38px rail badge. Server icon is SQUARE (round is people only). */
.srvhd{flex:none;position:relative}
.srvcover{height:100px;background:linear-gradient(135deg,#5a5a5a,#232323)}
/* only the bar (icon + name + chevron) is the menu button; the cover banner is
   decorative. The bar overlays the bottom of the banner as a transparent strip
   with a legibility scrim so the cover reads full-height underneath. */
.srvbar{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:center;gap:10px;width:100%;text-align:left;padding:8px var(--s3);
  background:linear-gradient(to top,rgba(0,0,0,.64),rgba(0,0,0,.12) 62%,rgba(0,0,0,0));cursor:pointer;transition:background var(--t)}
.srvbar:hover{background:linear-gradient(to top,rgba(0,0,0,.76),rgba(0,0,0,.26) 62%,rgba(0,0,0,.05))}
.srvchev{margin-left:auto;color:rgba(255,255,255,.72);transition:transform var(--t)}
.srvbar[aria-expanded="true"] .srvchev{transform:rotate(180deg);color:#fff}
.srvicon{width:40px;height:40px;flex:none;border-radius:var(--r);background:var(--ink);color:var(--on-ink);display:grid;place-items:center;font-weight:700;font-size:var(--fs-sm);box-shadow:0 0 0 2px rgba(255,255,255,.16);overflow:hidden}
.srvicon img{width:100%;height:100%;object-fit:cover}
.srvbar b{font-size:var(--fs-sm);color:#fff}
.chanbody{flex:1;overflow-y:auto;padding:var(--s3) var(--s2)}
.cgroup{margin-bottom:var(--s4)}
.cglabel{display:flex;align-items:center;padding:0 var(--s2) var(--s1);color:var(--muted);font-size:var(--fs-xs);font-weight:600}
.cglabel .ic{color:var(--muted)}
/* the group header is a button so its label can't be text-selected while you
   click to collapse it (accidental highlight was the bug). */
.cglabel .cgtoggle{flex:1;display:flex;align-items:center;min-width:0;background:none;color:inherit;font:inherit;font-weight:600;text-align:left;padding:0;cursor:pointer;user-select:none;transition:color var(--t)}
.cglabel .cgtoggle:hover{color:var(--soft)}
.cglabel .cgadd{margin-left:auto}
.cgadd{width:18px;height:18px;display:grid;place-items:center;border-radius:var(--r);color:var(--muted)}
.cgadd:hover{background:var(--plate);color:var(--ink)}
.cglabel .cgcaret{width:11px;height:11px;color:var(--muted);margin-right:2px;transition:transform var(--t)}
.cgroup.collapsed .cglabel .cgcaret{transform:rotate(-90deg)}
.cgroup.collapsed .crow,.cgroup.collapsed .vpeople{display:none}
.cgear{flex:none;width:18px;height:18px;display:grid;place-items:center;border-radius:var(--r);color:var(--muted);opacity:0;transition:opacity var(--t)}
.crow:hover .cgear{opacity:1}
.cgear:hover{background:var(--plate);color:var(--ink)}
.crow{display:flex;align-items:center;gap:8px;width:100%;text-align:left;padding:6px var(--s2);border-radius:var(--r);color:var(--soft);transition:background var(--t),color var(--t)}
.crow:hover{background:var(--plate)}
.crow.on{background:var(--plate);color:var(--ink);font-weight:600}
.crow .ic{color:var(--muted);width:15px;height:15px}
.crow .nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--fs-sm)}
.crow .ct{color:var(--muted);font-size:var(--fs-xs)}
/* P19 — unread dot (same family as presence dots; the only round chrome allowed). The bold
   name is the primary unread cue; this reinforces it at the row's right edge. */
.crow .unreaddot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--ink)}
.vpeople{padding:2px 0 4px 30px;display:flex;flex-direction:column;gap:4px}
.vp{display:flex;align-items:center;gap:7px;font-size:var(--fs-xs)}
.vp .live{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}

/* ── main chat pane ──────────────────────────────────────────────────────── */
.main{flex:1;min-width:0;display:flex;flex-direction:column;background:var(--paper);overflow:hidden;position:relative}
.newdiv{display:flex;align-items:center;gap:10px;margin:8px 0;color:var(--m1);font-size:var(--fs-xs);font-weight:600}
.newdiv::before,.newdiv::after{content:"";flex:1;height:1px;background:var(--m1);opacity:.5}
.jumpnew{position:absolute;left:50%;transform:translateX(-50%);bottom:96px;z-index:5;display:flex;align-items:center;gap:6px;padding:4px 12px;border-radius:var(--r);background:var(--ink);color:var(--on-ink);font-size:var(--fs-xs);font-weight:500;box-shadow:0 6px 20px rgba(0,0,0,.25)}
.jumpnew.gone{display:none}
/* connection-lost / reconnecting banner (Realtime dropped) */
.offlinebar{flex:none;display:flex;align-items:center;justify-content:center;gap:8px;padding:7px;background:var(--danger);color:var(--on-ink);font-size:var(--fs-xs);font-weight:500}
.offlinebar[hidden]{display:none}   /* a class display: rule beats UA [hidden] — restate it */
.offlinebar .odot{width:7px;height:7px;border-radius:50%;background:var(--on-ink);animation:opulse 1.1s infinite}
@keyframes opulse{0%,100%{opacity:.35}50%{opacity:1}}
.mainhd{flex:none;height:46px;display:flex;align-items:center;gap:var(--s3);padding:0 var(--s4);background:var(--surface)}   /* P18: unified header height 46 + --s4 inset + --surface */
.mainhd .ic{color:var(--muted)}
.mainhd .t{font-weight:600;font-size:var(--fs)}
.mainhd .sub{color:var(--muted);font-size:var(--fs-sm);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.mainhd .acts{margin-left:auto;display:flex;gap:var(--s1)}
.stream{flex:1;overflow-y:auto;padding:var(--s4)}
/* P20 — "Load earlier messages" sentinel at the top of the windowed stream */
.loadearlier{display:flex;justify-content:center;padding:var(--s2) 0 var(--s3)}
.loadearlier[hidden]{display:none}   /* B34: this display:flex beats the UA [hidden] — restate it, or the "Load earlier" button shows when hasMore is false */
.day{display:flex;align-items:center;gap:var(--s3);margin:var(--s3) 0;color:var(--muted);font-size:var(--fs-xs)}
.day::before,.day::after{content:"";flex:1;height:1px;background:var(--line)}
.msg{position:relative;display:flex;gap:var(--s3);padding:5px var(--s2);border-radius:var(--r)}
.msg:hover{background:var(--surface)}
/* permalink arrival: a brief background pulse marks the linked message, then settles —
   background step only, no border or shadow, per the "surfaces separate by step" rule. */
.msg.flash{animation:msgflash 1.7s ease-out}
@keyframes msgflash{0%,25%{background:var(--plate)}100%{background:transparent}}
.msg .bd{flex:1;min-width:0}
.msg .by{display:flex;align-items:baseline;gap:var(--s2)}
.msg .by time{font-size:var(--fs-mi);color:var(--muted)}
.msg .tx{font-size:var(--fs-sm);color:var(--soft);margin-top:1px}
.msg .tx .men{font-weight:600}
.msg .tx .edited{color:var(--muted);font-size:var(--fs-xs)}
/* inline markdown in a message/thread/pin body */
.tx code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.92em;background:var(--plate);border-radius:3px;padding:1px 5px}
.tx a{color:var(--ink);text-decoration:underline;text-underline-offset:2px}
.tx strong{font-weight:700}
.tx del{opacity:.75}
/* username: the ONE place member colour renders (server surface). The hue is set
   inline per-member (style.color:var(--mN)) so all 30 hues work, not just 1–6. */
.u{font-weight:600}
.hoveracts{position:absolute;top:-10px;right:10px;display:none;gap:2px;background:var(--paper);border-radius:var(--r);box-shadow:0 2px 10px rgba(0,0,0,.16);padding:2px}
.msg:hover .hoveracts{display:flex}
.hoveracts .iconbtn{width:26px;height:26px;color:var(--muted)}
.reactions{display:flex;gap:6px;margin-top:6px}
.react{display:inline-flex;align-items:center;gap:5px;background:var(--plate);border-radius:var(--r);padding:2px 8px;font-size:var(--fs-xs);color:var(--soft);cursor:pointer}
.react.on{box-shadow:inset 0 0 0 1px var(--ink)}
.react .n{font-variant-numeric:tabular-nums}
.reply{margin-top:5px;font-size:var(--fs-xs);color:var(--muted);display:inline-flex;align-items:center;gap:5px;cursor:pointer}
.reply:hover{color:var(--ink)}

/* ── file shares in a message (P4.7) ─────────────────────────────────────── */
/* filename leads; a forwarded message is a left-ruled quote naming its source. */
.fwd,.fwdquote{border-left:2px solid var(--line2);padding:2px 0 2px 10px;margin-top:6px;max-width:460px}
.fwd .src,.fwdquote .by{font-size:var(--fs-xs);color:var(--muted);margin-bottom:3px;display:flex;align-items:center;gap:6px}
.fwd .src .ic{width:12px;height:12px}
.fwd .tx,.fwdquote .tx{font-size:var(--fs-sm);color:var(--soft)}
.fwdquote{background:var(--surface);border-radius:var(--r);padding:9px 11px 9px 12px;margin-top:0}
/* Report modal (§C.4 S6): reason radio list. Own dot class (.repdot) — the app's .rdot is
   the rail unread dot, a different element. */
.repopts{display:flex;flex-direction:column;gap:6px}
.repopt{display:flex;align-items:center;gap:10px;width:100%;text-align:left;padding:8px 12px;border-radius:var(--r);background:var(--surface);font-size:var(--fs-sm);color:var(--soft);transition:background var(--t)}
.repopt:hover{background:var(--plate)}
.repopt .repdot{flex:none;width:15px;height:15px;border-radius:50%;border:2px solid var(--line2);transition:border-color var(--t)}
.repopt.on{color:var(--ink)}
.repopt.on .repdot{border-color:var(--ink);background:radial-gradient(var(--ink) 0 4px,transparent 5px)}
/* Forward modal: the pick-target channel list (checkbox + # + name) */
.fwdpicks{display:flex;flex-direction:column;gap:1px;max-height:220px;overflow-y:auto;margin-top:2px}
.fwdpick{display:flex;align-items:center;gap:9px;padding:7px 6px;border-radius:var(--r);cursor:pointer;font-size:var(--fs-sm);color:var(--soft)}
.fwdpick:hover{background:var(--surface)}
.fwdpick .ic{color:var(--muted);flex:none}
.fileclump{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:4px;margin-top:8px;max-width:320px}
.fileclump .fc{display:flex;align-items:center;gap:8px;background:var(--plate);border-radius:var(--r);padding:8px 10px;font-size:var(--fs-xs);cursor:pointer;min-width:0;transition:background var(--t)}
.fileclump .fc:hover{background:var(--paper1)}
.fileclump .fc .ic{color:var(--muted);flex:none;width:15px;height:15px}
.fileclump .fc .n{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fileclump .fc.more{justify-content:center;color:var(--muted);font-weight:600}
.filecard{margin-top:8px;max-width:420px;background:var(--plate);border-radius:var(--r);overflow:hidden;cursor:pointer;transition:background var(--t);display:block;width:100%;text-align:left}
.filecard:hover{background:var(--paper1)}
.fcwave{height:74px;padding:10px 14px;background:var(--paper1);display:flex;align-items:center;gap:10px;color:var(--muted)}
.fcwave .fic{width:24px;height:24px}
.fcwave .ext{font-size:12px;font-weight:700;color:var(--soft);letter-spacing:.5px}
.fbody{padding:9px var(--s3)}
.fname{display:flex;align-items:center;gap:8px;font-size:var(--fs-sm);font-weight:600}
.ftags{display:flex;flex-wrap:wrap;gap:10px;margin-top:8px;align-items:center}
/* inline attachment actions, solid chips (no transparency) */
.filecardwrap{position:relative;display:inline-block;max-width:100%}
.fcacts{position:absolute;top:8px;right:8px;display:none;gap:3px}
.filecardwrap:hover .fcacts{display:flex}
.fcacts button{width:26px;height:26px;display:grid;place-items:center;border-radius:var(--r);background:var(--paper);color:var(--soft);box-shadow:0 0 0 1px var(--line)}
.fcacts button:hover{background:var(--surface);color:var(--ink)}
.fcacts button:active{background:var(--ink);color:var(--on-ink)}

/* ── composer (P4.6) ─────────────────────────────────────────────────────── */
.composer{flex:none;padding:0 var(--s4) var(--s4)}
.composer .field{padding:9px var(--s3)}
/* send button: a real 26px grid-centred icon box (it set no size of its own and
   was previously sized only by the UA button padding we now reset away). */
.composer .field .snd{flex:none;width:26px;height:26px;display:grid;place-items:center;border-radius:var(--r);color:var(--muted)}
.composer .field .snd:hover{color:var(--ink)}
.composernote{display:flex;align-items:center;gap:8px;padding:8px 12px;margin-bottom:8px;background:var(--surface);border-radius:var(--r);font-size:var(--fs-sm);color:var(--soft)}
.composernote[hidden]{display:none}   /* a class display: rule beats UA [hidden] — restate it */
.composernote .ic{flex:none;color:var(--muted)}
.composer.disabled .richcomposer,.composer.disabled .field{opacity:.5;pointer-events:none}
.richcomposer{border:1px solid var(--line2);border-radius:var(--r);overflow:hidden;transition:border-color var(--t)}
.richcomposer:focus-within{border-color:var(--ink)}
.richcomposer .field{border:0;border-radius:0;background:var(--paper)}
.typing{flex:none;height:20px;padding:0 var(--s4) 2px;font-size:var(--fs-xs);color:var(--muted);display:flex;align-items:center;gap:7px}
.typing[hidden]{display:none}
.typing .dots{display:inline-flex;gap:3px}
.typing .dots i{width:4px;height:4px;border-radius:50%;background:var(--muted);animation:tblink 1.2s infinite}
.typing .dots i:nth-child(2){animation-delay:.2s}.typing .dots i:nth-child(3){animation-delay:.4s}
@keyframes tblink{0%,60%,100%{opacity:.25}30%{opacity:1}}

/* ── channel header tabs + Pins / Files panels (P4.4) ────────────────────── */
.chtabs{flex:none;display:flex;gap:2px;padding:0 var(--s4);background:var(--surface)}
.chtab{display:flex;align-items:center;gap:7px;padding:9px 10px;color:var(--soft);font-size:var(--fs-sm);position:relative}
.chtab .ic{color:var(--muted)}
.chtab .n{color:var(--muted);font-size:var(--fs-xs)}
.chtab:hover{color:var(--ink)}
.chtab.on{color:var(--ink);font-weight:600}
.chtab.on .ic{color:var(--ink)}
.chtab.on::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--ink)}
.chtab:focus-visible{outline:none;box-shadow:inset 0 -2px 0 var(--ink)}
.chpanel{flex:1;overflow-y:auto;padding:var(--s4) var(--s5)}
.chpanel[hidden]{display:none}
.chpanel .lb{color:var(--muted);font-size:var(--fs-xs);font-weight:600;margin-bottom:var(--s3)}
.chfilesbar{display:flex;align-items:center;gap:var(--s2);padding:var(--s3) var(--s4) 0}
.chfilesbar .field{max-width:360px;flex:1}
.pinrow{display:flex;gap:var(--s3);padding:12px;border-radius:var(--r);max-width:720px}
.pinrow:hover{background:var(--surface)}
.pinrow:hover .unpin{opacity:1}
.pinrow .bd2{flex:1;min-width:0}
.pinrow .pinby{font-size:10px;color:var(--muted);margin-bottom:5px;display:flex;align-items:center;gap:5px}
.pinrow .by{display:flex;align-items:baseline;gap:8px}.pinrow .by time{font-size:10px;color:var(--muted)}
.pinrow .tx{font-size:var(--fs-sm);color:var(--soft);margin-top:2px}
.pinrow .unpin{opacity:0;color:var(--muted);align-self:flex-start}.pinrow .unpin:hover{color:var(--m1)}

/* ── members rail (P4.9) ─────────────────────────────────────────────────── */
.mem{flex:none;width:var(--mem);background:var(--surface);overflow-y:auto;padding:var(--s4) 0}
.mem[hidden]{display:none}
.memg{padding:0 var(--s3) var(--s4)}
.memg .lb{padding:0 var(--s2) 7px;color:var(--muted);font-size:var(--fs-xs);font-weight:600}
.mrow{display:flex;align-items:center;gap:var(--s2);padding:5px var(--s2);border-radius:var(--r);transition:background var(--t);width:100%;text-align:left}
.mrow:hover{background:var(--plate)}
.mrow .av{position:relative}
.mrow .pr{position:absolute;right:-2px;bottom:-2px;width:9px;height:9px;border-radius:50%;background:var(--ink);border:2px solid var(--surface)}
.mrow .pr.idle{background:var(--muted)}
.mrow .pr.dnd{background:var(--danger)}
.mrow .pr.off{background:var(--muted)}
.mrow .info{min-width:0}
.mrow .nm{font-size:var(--fs-sm);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mrow .doing{font-size:var(--fs-mi);color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mrow.off{opacity:.55}

/* ── thread pane (P4.8) ──────────────────────────────────────────────────── */
.threadpane{flex:none;width:360px;background:var(--surface);display:flex;flex-direction:column;overflow:hidden}
.threadpane[hidden]{display:none}
.tphd{height:48px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 var(--s4);background:var(--plate)}
.tphd .lbl{display:flex;align-items:center;gap:8px;font-weight:600;font-size:var(--fs-sm)}
.tphd .lbl .sub{color:var(--muted);font-size:var(--fs-xs);font-weight:400}
.tphd .ic{color:var(--muted)}
.tpbody{flex:1;overflow-y:auto;padding:var(--s4)}
.tpdiv{display:flex;align-items:center;gap:var(--s3);color:var(--muted);font-size:var(--fs-xs);margin:var(--s2) 0}
.tpdiv::after{content:"";flex:1;height:1px;background:var(--line)}
.alsosend{display:flex;align-items:center;gap:8px;font-size:var(--fs-xs);color:var(--muted);margin-top:9px;cursor:pointer}
.alsosend .cbx{width:15px;height:15px;flex:none;border-radius:3px;background:var(--paper);box-shadow:inset 0 0 0 1.5px var(--line2);display:grid;place-items:center;color:var(--paper)}
.alsosend.on .cbx{background:var(--ink);box-shadow:none}
.alsosend.on .cbx svg{display:block}.alsosend .cbx svg{display:none;width:11px;height:11px}

/* ── voice minibar (WIP: voice ships v2; grayed + a build note) ───────────── */
.voicemini{flex:none;margin:0 var(--s2) var(--s2);padding:8px 9px;background:var(--plate);border-radius:var(--r);opacity:.62;cursor:pointer}
.voicemini:hover{opacity:.78}
.voicemini .vmtop{display:flex;align-items:center;gap:8px}
.voicemini .vmdot{width:8px;height:8px;border-radius:50%;background:var(--muted);flex:none}
.voicemini .vminfo{min-width:0;flex:1}
.voicemini .vminfo b{display:block;font-size:var(--fs-xs);color:var(--ink);line-height:1.2}
.voicemini .vminfo small{display:block;font-size:10px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.voicemini .vmwip{display:flex;align-items:center;gap:6px;margin-top:6px;font-size:10px;color:var(--muted)}
.voicemini .vmwip .ic{width:11px;height:11px}

/* ── empty + loading states (workspace edge states) ──────────────────────── */
/* Empty state — compact + terse, and NOT vertically sprawling (owner: too much vertical
   space). Content-height, top-anchored with a modest offset — no flex:1 fill / centre that
   floats it in the middle of a tall pane. Small icon, tight rhythm, short copy. */
/* P1: an empty-state block centers BOTH axes within its pane, not top-anchored. min-height:100%
   fills the host pane (every host is a definite-height flex context — .stream/.panebody/.main are
   flex:1, .notif/.dmmain are flex columns) so justify-content:center has room to work; this one
   global rule replaces the per-pane `margin:auto` patches (removed from content.css). */
.emptystate{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;min-height:100%;padding:var(--s4);gap:var(--s1);color:var(--muted)}
.emptystate .eic{width:24px;height:24px;color:var(--line2);margin-bottom:var(--s1)}
.emptystate h3{font-size:var(--fs);font-weight:600;color:var(--soft);margin:0}
.emptystate p{font-size:var(--fs-sm);max-width:280px;line-height:1.4;color:var(--muted);margin:0}
.emptystate .btn{margin-top:var(--s3)}
/* C31: an empty folder reads as a real DROP ZONE — a dashed panel inset from the pane edges, not a
   bare line of text. The actual drag-over feedback is the pane's `.dropping` overlay (base.css); this
   is the resting affordance that says "you can drop here". Square corners at --r (media stays square). */
.emptystate.dropzone{margin:var(--s4);border:2px dashed var(--line2);border-radius:var(--r);min-height:calc(100% - 2*var(--s4))}
.skel{background:var(--surface);border-radius:var(--r);position:relative;overflow:hidden}
.skel::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(255,255,255,.35),transparent);transform:translateX(-100%);animation:shimmer 1.3s infinite}
:root:not([data-theme="light"]) .skel::after{background:linear-gradient(90deg,transparent,rgba(255,255,255,.06),transparent)}
:root[data-theme="dark"] .skel::after{background:linear-gradient(90deg,transparent,rgba(255,255,255,.06),transparent)}
@keyframes shimmer{100%{transform:translateX(100%)}}
.sk-av{width:34px;height:34px;border-radius:50%;flex:none}
.skelmsg{display:flex;gap:10px;padding:9px 0}
.skelmsg .sk-av{width:34px;height:34px}
.skelmsg .sk-bd{flex:1;display:flex;flex-direction:column;gap:7px;max-width:60%}
.skelmsg .sk-line{height:10px;border-radius:var(--r)}
.skelrow{display:flex;align-items:center;gap:10px;padding:11px 2px}
.skelrow .sk-av{width:32px;height:32px}
.skelrow .sk-bd{flex:1;display:flex;flex-direction:column;gap:6px;max-width:70%}
.skelrow .sk-line{height:10px;border-radius:var(--r)}

/* ── file cards for the channel Files tab (P4.4). The card DISPLAY archetype;
   selection/marquee/lightbox interaction is added by P5's explorer. ───────── */
.masonry{column-width:230px;column-gap:var(--s5)}
.masonry.even{column-width:auto;column-gap:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(184px,1fr));gap:var(--s4)}
/* P14 · LARGE density: bigger content thumbnails, a touch more gap to breathe with 2-line titles.
   P32: the thumbnail min-width is now driven by --tile (set per density stage on the .exview wrap),
   so the grid re-columns as the density slider moves. 232px stays the default when --tile is unset. */
.masonry.even.exlarge{grid-template-columns:repeat(auto-fill,minmax(var(--tile,232px),1fr));gap:var(--s5) var(--s4)}
/* P32 · density easing — dense stages tighten gap + card chrome + the title→tags spacing (the owner's
   "mockup density + title-to-tags gap"); the large stage breathes more. Stages 3/4 keep the default. */
.exview[data-density="1"] .masonry.even.exlarge,
.exview[data-density="2"] .masonry.even.exlarge{gap:var(--s3)}
.exview[data-density="5"] .masonry.even.exlarge{gap:var(--s5)}
.exview[data-density="1"] .cardfoot,
.exview[data-density="2"] .cardfoot{gap:3px;padding:6px 8px}
.exview[data-density="1"] .cardfoot .fname,
.exview[data-density="2"] .cardfoot .fname{font-size:var(--fs-xs)}
.exview[data-density="1"] .cardfoot .ftags,
.exview[data-density="2"] .cardfoot .ftags{gap:4px;margin-top:0}
.exview[data-density="5"] .cardfoot{gap:8px;padding:10px 12px}
.masonry.even .card{margin-bottom:0}
.masonry.even .media{aspect-ratio:1;overflow:hidden;display:grid}
.masonry.even .media>.shot{width:100%!important;height:100%!important;aspect-ratio:auto!important}
.masonry.even .media.audio,.masonry.even .media.file{aspect-ratio:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px}
.card{display:inline-block;width:100%;margin-bottom:var(--s5);cursor:pointer;text-align:left;break-inside:avoid}
/* a previewable thumbnail's square container is TRANSPARENT (image/video fills
   it); only non-previewable tiles (.media.file, .media.audio) keep a surface tile. */
.card .media{position:relative;width:100%;overflow:hidden;background:transparent}
/* a non-previewable tile's media plane sits one step off the --surface band beneath it (--paper1),
   so the tile reads as two planes (media + band) instead of one flat block (thumbnail redesign). */
.media.file,.media.audio{background:var(--paper1);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;color:var(--muted)}
.media.file .fic,.media.audio .fic{width:30px;height:30px;color:var(--muted)}
.media.file .ext,.media.audio .ext{font-size:12px;font-weight:700;color:var(--soft);letter-spacing:.5px}
.shot{width:100%;display:block;background:linear-gradient(150deg,#9a9a9a,#4d4d4d)}
.shot.b{background:linear-gradient(150deg,#8a8a8a,#3a3a3a)}
.shot.c{background:linear-gradient(150deg,#a7a7a7,#606060)}
.playover{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:46px;height:46px;border-radius:50%;background:rgba(0,0,0,.42);color:#fff;display:grid;place-items:center}
/* Tile band (thumbnail redesign, owner 2026-08-31) — replaces the loose .title/.who that floated
   under the media on the pane background (a "square with text"). A single-line name (scrolls on
   hover for long names, see wireNameScroll) + a row of a couple tags (left) and the uploader pfp
   (right), on a --surface plane. A fixed one-line name + one-row layout means every card is the
   same height without the old 2-line clamp, so grid rows stay even. */
.cardfoot{display:flex;flex-direction:column;gap:6px;padding:8px 10px;background:var(--surface);border-radius:0 0 var(--r) var(--r)}
.cardfoot .fname{position:relative;overflow:hidden;white-space:nowrap;font-size:var(--fs-sm);line-height:1.3;color:var(--ink);font-weight:600}
.cardfoot .fname .fnt{display:inline-block;white-space:nowrap;transform:translateX(0);transition:transform 0s linear;will-change:transform}
/* the mask only appears once JS confirms the name overflows — short names stay crisp to the edge */
.cardfoot .fname.scrolls{-webkit-mask-image:linear-gradient(90deg,#000 84%,transparent);mask-image:linear-gradient(90deg,#000 84%,transparent)}
.cardfoot .frow{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:20px}
.cardfoot .ftags{display:flex;flex-wrap:nowrap;gap:4px;overflow:hidden;min-width:0}
.cardfoot .ftags .tchip{flex:none;font-size:var(--fs-mi);padding:1px 6px}
.cardfoot .who{flex:none;display:flex;align-items:center;gap:6px;color:var(--muted);font-size:var(--fs-xs);min-width:0;max-width:64%}
.cardfoot .who .uname{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* the media plane rounds its TOP corners only; the band rounds the bottom — together a --r tile */
.masonry.even .card{background:var(--surface);border-radius:var(--r)}
.masonry.even .card .media{border-radius:var(--r) var(--r) 0 0}
.masonry.even .card .media:not(.file):not(.audio):not(.fold){background:var(--paper1)}
