/* ui/modules/shell.css - the page shell's own rules, loaded by ui/index.html under its build's
 * /b/<build>/ prefix (immutable, a new URL every deploy). The design tokens these rules read stay
 * inline in index.html's first <style>, generated by tools/tokens.py; the owner's appearance
 * overrides follow this file in the #ala-look slot, so they win the cascade. */
*{box-sizing:border-box}
/* no visible scrollbar anywhere; scrollTop still moves by wheel/trackpad/touch/keyboard. */
*{scrollbar-width:none}*::-webkit-scrollbar{display:none}
/* standalone has no native bounce-to-refresh or long-press callout of its own to disable */
html,body{height:100%;margin:0;background:var(--bg);color:var(--ink);font:var(--t-body)/1.5 var(--sans);-webkit-font-smoothing:antialiased;overscroll-behavior:none}
button{font:inherit}
/* Every control shows the pointer from this one rule, so a button, link or role=button a module draws later never shows the text cursor; a disabled one shows the arrow. */
button,a[href],[role=button]{cursor:pointer}button:disabled,[aria-disabled=true]{cursor:default}
button,.th,.ochip,.card h4,.chip,.upd{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* Line breaking: titles balance their lines, prose avoids a one-word last line. The tokens are --wrap-title and --wrap-prose. */
h1,h2,h3,h4,.dch h3,.sec>summary h3,.c-sheet-t{text-wrap:var(--wrap-title)}
p,li,dd,blockquote,.shln,.ac-now,.dstate>span,.sp-now span,.mx-empty span,.mx-text,.c-field-n,.c-dialog-p,.cb-m,.rdesc,.hnar,.pu-t,.cx-lede,.cx-purpose,.ar-fb,.agrb,.lrbody,.shp1,.hbrief,.hres,.mtg-ag,.mtg-card,.mtg-flt,.mtg-flq,.sit,.bd,.q,.pr,.gr,.cue,.trap,.body.p,.sub,.vr-find,.vr-note,.c-tr-p,.c-tr-u,.jp-goal,.jp-spoken,.jb-pq,.nw-p,.nw-gn,.nw-state,.cm-scroller,.bo-md{text-wrap:var(--wrap-prose)}
/* text-wrap inherits: the page base gives every container no rule names the prose token, so a module never repeats it. Headings above override with balance; controls keep their one-line labels through their own nowrap. */
body{text-wrap:var(--wrap-prose)}
/* Motion (docs/DESIGN-SYSTEM.md, Motion). Durations and curves are the generated --d-*, --m-* and
   --ease-* tokens; the keyframes every module animates with are the set below, named by what they say,
   so "pending" is one beat on every page. A module defines its own keyframe only for a character's
   own body (the voice being, the presence avatars, the 3D cast).
   ala-pulse   a mark dims and returns: running, waiting, pending          (tier 2, --m-pulse)
   ala-spin    one full turn: a working spinner, the brand mark            (tier 2, --m-spin, --d-orbit)
   ala-breathe a body swells a little and settles: a presence at rest      (tier 2, --d-breathe)
   alaring     a ring leaves a control and fades: listening, a new item    (tier 2, --m-pulse)
   ala-shimmer light passes over a skeleton                                (tier 2, --m-shimmer)
   ala-rise    a new item rises into place                                 (tier 3, --m-rise)
   ala-flash   a changed row washes in --flash and fades back              (tier 3, --m-flash)
   ala-fade    a surface fades in                                          (tier 1)
   Every loop animates transform or opacity only, so it never repaints the page. alaring scales the
   control's own ::after, which draws the ring (a box-shadow that holds still) and rests hidden. */
/* The knobs a consumer sets on the animated element: how far alaring grows (--ring-to), where
   ala-rise starts (--rise) and the tone ala-flash washes in (--flash). */
:root{--dash-w:1120px;--dash-w-wide:1360px;--ring-to:1.5;--rise:var(--sp-1);--flash:var(--accent-wash)}
@keyframes ala-pulse{50%{opacity:var(--o-dim)}}
@keyframes ala-spin{to{transform:rotate(1turn)}}
@keyframes ala-breathe{50%{transform:scale(1.035)}}
@keyframes alaring{from{opacity:var(--o-soft);transform:none}to{opacity:0;transform:scale(var(--ring-to))}}
@keyframes ala-shimmer{to{transform:translateX(100%)}}
@keyframes ala-rise{from{opacity:0;transform:translateY(var(--rise))}}
@keyframes ala-flash{from{background-color:var(--flash)}}
@keyframes ala-fade{from{opacity:0}}
/* Motion off. html[data-motion-off] is the one hook (ui/components/base.js keeps it in step with the
   app's preference and the OS setting); every module's still state keys off it, never the media
   query alone. Elements and their ::before and ::after finish at once: a loop plays one instant
   iteration and rests on its unanimated pose, a one-shot ends so its animationend still fires. The
   hook is repeated so the rule outranks any class selector a module sets a duration with, without force. */
html[data-motion-off][data-motion-off][data-motion-off][data-motion-off] *,html[data-motion-off][data-motion-off][data-motion-off][data-motion-off] ::before,html[data-motion-off][data-motion-off][data-motion-off][data-motion-off] ::after{animation-duration:.001ms;animation-iteration-count:1;animation-delay:0s;transition-duration:.001ms;transition-delay:0s}
html[data-motion-off][data-motion-off][data-motion-off][data-motion-off]::view-transition-group(*),html[data-motion-off][data-motion-off][data-motion-off][data-motion-off]::view-transition-old(*),html[data-motion-off][data-motion-off][data-motion-off][data-motion-off]::view-transition-new(*){animation-duration:.001ms}

/* Shared parts. A module that draws a button, chip, input, caps label or list row uses these
   classes (or joins the selector lists here) instead of restating sizes, so every one of a kind
   is the same size on every surface. On a phone each is a 44px touch target. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);height:var(--ctl);padding:0 var(--sp-3);border:1px solid var(--hairline-2);border-radius:var(--r-2);background:none;color:var(--muted);font:var(--t-ui)/1 var(--sans);white-space:nowrap;cursor:pointer;text-decoration:none;transition:color var(--m-fast),border-color var(--m-fast),background var(--m-fast)}
.btn:hover{color:var(--ink);border-color:var(--hairline-3)}
.btn:disabled{opacity:var(--o-disabled);cursor:default}
.btn.pri{background:var(--accent);border-color:var(--accent);color:var(--on-accent);font-weight:var(--fw-strong)}
.btn.pri:hover{background:var(--accent-hi);border-color:var(--accent-hi)}
.chip,.ask button,.afilt button{display:inline-flex;align-items:center;gap:var(--sp-2);min-height:var(--ctl);padding:0 var(--sp-3);border:1px solid var(--hairline-2);border-radius:var(--r-pill);background:var(--surface-3);color:var(--ink);font:var(--t-ui)/var(--lh-ui) var(--sans);text-align:left;text-decoration:none;cursor:pointer;transition:color var(--m-fast),border-color var(--m-fast),background var(--m-fast)}
.chip:hover,.chip:focus-visible{border-color:var(--hairline-3)}
.inp{height:var(--row);padding:0 var(--sp-3);border:1px solid var(--hairline-2);border-radius:var(--r-3);background:var(--surface);color:var(--ink);font:var(--t-body) var(--sans);outline:0}
.inp:focus{border-color:var(--accent)}
.caps,.rlbl,.mnpop .g,.card h4{font:var(--fw-strong) var(--t-meta)/var(--lh-ui) var(--sans);color:var(--faint)}
@media(max-width:760px){
  .btn,.inp{height:var(--row-touch)}
  /* iOS zooms the page on focus for any input under 16px */
  .inp{font-size:var(--t-user)}
  /* A chip stays 36 tall to the eye; the 4px band above and below it widens the hit area to 44
     without overlapping the next row of chips, which sits 8px away. The band is placed from
     inside the chip's 1px border, hence the extra 1px. */
  .chip,.ask button,.afilt button{position:relative;min-height:var(--row)}
  .chip::after,.ask button::after,.afilt button::after{content:"";position:absolute;inset:calc((var(--row) - var(--row-touch)) / 2 - 1px) 0}
}

/* dvh tracks iOS toolbars; --vvh (webapp.js, visualViewport) narrows it under the keyboard too */
/* --navc: the sidebar's column, set by its state (ui/components/sidebar.js) */
.app{--navc:var(--nav-w);display:grid;grid-template-columns:var(--navc) 1fr var(--hz-w);grid-template-rows:auto 1fr;height:100vh;height:100dvh;height:var(--vvh,100dvh);overflow:hidden;background:var(--bg)}
.app[hidden]{display:none}
.app.rc{--navc:var(--nav-rail-w)}
.app.nh{--navc:0px}
.app.nh:not(.peek) .rail:not([data-leaving]){visibility:hidden}
.app.nh:not(.peek) .rail{overflow:hidden}
.app.nh .top #menu{display:grid}
.unreach{display:grid;place-items:center;align-content:center;min-height:100vh;min-height:100dvh;padding:0 var(--inset-page);box-sizing:border-box;color:var(--muted);font-size:var(--t-body)}
/* The same error state the library draws (components/states.js), restated because the boot error shows when no module could load. */
.unreach .dstate{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:var(--sp-3);text-align:center;color:var(--muted)}
.unreach .dstate span{text-wrap:var(--wrap-prose)}
.unreach .dstate svg{width:var(--icon-sm);height:var(--icon-sm);flex:none;fill:none;stroke:var(--error);stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round}
.unreach .btn{color:var(--ink);background:var(--surface-2)}
/* The boot screen fades in after the skeleton delay, so a boot that is quick never flashes it, and
   goes the moment anything else holds the page: the code that shows the app, the sign-in card or the
   unreachable message sets body.held, because a body-level :has() makes WebKit restyle the whole
   document on every DOM change. The wheel stays still: it turns only for an open turn. */
.boot{display:grid;place-items:center;align-content:center;gap:var(--sp-3);height:100%;color:var(--muted);font-size:var(--t-ui);animation:ala-fade var(--d-fast) var(--ease-out) var(--d-fast) both}
.boot p{margin:0}
.boot .sk{width:var(--tile-min);height:var(--meter-h)}
body.held .boot{display:none}
::view-transition-group(*){animation-duration:var(--nav-d);animation-timing-function:var(--ease-out)}

/* grid-column pinned on all three: a peeked, fixed-position rail leaves grid flow, else
   auto-placement slides .main/.harness left. */
.rail{grid-column:1;grid-row:1/3;background:var(--bg);display:flex;flex-direction:column;min-width:0;min-height:0;height:100%;padding:env(safe-area-inset-top) var(--sp-2) env(safe-area-inset-bottom);view-transition-name:rail}
/* The brand row is the top bar's height, so the mark and the thread title share one centre line. */
/* The mark's left inset is where the collapsed rail centres it, so the collapse toggle itself never
   moves or resizes: only the wordmark comes and goes. */
.brand{display:flex;align-items:center;gap:var(--sp-2);flex:none;height:var(--bar);padding:0 var(--sp-2) 0 calc((var(--nav-rail-w) - var(--icon-lg)) / 2 - var(--sp-2))}
.ib{width:var(--ctl);height:var(--ctl);display:grid;place-items:center;border:0;background:none;color:var(--faint);border-radius:var(--r-2);cursor:pointer;padding:0;flex:none}
.ib:hover{color:var(--ink);background:var(--surface-2)}
.ib svg,.comp .att svg,.comp .mic svg,.annotbar button svg,.new .pl svg{width:var(--icon-sm);height:var(--icon-sm);stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
/* the mark is the rail toggle; collapsed shows the mark and nothing else */
.brand .dbtn.brandbtn{width:auto;gap:var(--sp-2);padding:0;margin:0;color:inherit;font:inherit}
/* a disc control draws its children through the icon slot, so its empty label span takes no room */
.dbtn.disc>.dbl:empty{display:none}.dbtn.disc svg{margin:0}
/* Collapsed rail: a proper icon rail. The logo centres on a grid box, not a flex row with a
   hidden label (residual flex gap read as off-centre); New thread and every thread become
   square glyph buttons instead of vanishing. */
.app.rc:not(.peek) .rail{padding:0 var(--sp-2)}
.app.rc:not(.peek) .rail .brand{display:grid;place-items:center;padding:0}
.app.rc:not(.peek) .rail .nm,.app.rc:not(.peek) .rail small{display:none}
.app.rc:not(.peek) .dbtn.new{justify-content:center;gap:0;width:var(--row);height:var(--row);padding:0;margin:0 auto var(--sp-1);border-radius:var(--r-3)}
.app.rc:not(.peek) #railsearch{margin-bottom:var(--sp-2)}
.app.rc:not(.peek) .new .tx{display:none}
.app.rc:not(.peek) .threads{padding:0}
.app.rc:not(.peek) .rlbl{display:none}
.app.rc:not(.peek) .th{position:relative;justify-content:center;gap:0;width:var(--row);height:var(--row);padding:0;margin:0 auto var(--sp-1);border-radius:var(--r-3);transition:background var(--m-fast)}
.app.rc:not(.peek) .th .tt,.app.rc:not(.peek) .th .acts,.app.rc:not(.peek) .th .pinmark,.app.rc:not(.peek) .th-ava{display:none}
.app.rc:not(.peek) .th .ic{display:grid;place-items:center;width:100%;height:100%;border-radius:var(--r-3);background:var(--surface-2);color:var(--muted);font:var(--fw-strong) var(--t-ui) var(--sans);text-transform:uppercase}
.brand .mk{width:var(--icon-lg);height:var(--icon-lg);border-radius:var(--r-2);background:var(--surface-2);position:relative;flex:none;overflow:hidden;box-shadow:inset 0 0 0 1px var(--hairline-3)}
/* 2px: the colour ring's optical inset inside the tile */
.brand .mk::before{content:"";position:absolute;inset:2px;border-radius:50%;background:var(--brand-gradient)}
/* The wheel turns only while a turn is open, so motion on this page means exactly one thing.
   It carries no claim about how the work is going, which a colour change would. */
body.busy .brand .mk::before{animation:ala-spin var(--d-orbit) linear infinite}
.brand .mk::after{content:"";position:absolute;inset:var(--sp-2);border-radius:50%;background:var(--surface-2)}
.brand .nm{font-size:var(--t-ui);font-weight:var(--fw-bold);letter-spacing:.17em;text-transform:uppercase}
.brand small{color:var(--faint);font-size:var(--t-meta);font-family:var(--mono);margin-left:auto}
/* Rail rows (New, Search, threads, the footer control and its menu) share one grid: a 16px glyph
   column at the rail's inset, text 8px after it, one height, one type size, one radius. */
.dbtn.new{gap:var(--sp-2);margin:0 0 var(--sp-1);font-size:var(--t-body);font-weight:var(--fw-strong)}
.new .pl{width:var(--icon-sm);flex:none;display:grid;place-items:center;color:var(--faint)}
#railsearch{margin:0 0 var(--sp-2);color:var(--faint);font-weight:var(--fw-regular)}
/* The palette's shortcut, shown at the row's end where a keyboard is likely; the row's title names it too. */
#railsearch .tx{flex:1;min-width:0}
#railsearch .kh{flex:none;font:var(--t-meta) var(--sans);color:var(--faint);padding:0 var(--sp-1);border-radius:var(--r-1);background:var(--surface-3)}
.app.rc:not(.peek) #railsearch .kh,.c-sheet-b>.rail #railsearch .kh{display:none}
@media(hover:none){#railsearch .kh{display:none}}
/* the margin opens a real gap above the footer icons (measured 0px without it, flush enough to
   read as the last tile sitting under them); the mask fades the bottom edge when it overflows,
   the no-scrollbar hint (global rule below). min-height:0 is the standing belt for the buckle. */
.threads{overflow:auto;flex:1;min-height:0;margin-bottom:var(--sp-1);padding:0 0 var(--sp-2)}
/* styled and wired by ui/modules/rail.js; invisible and unclickable until then, not a raw button, but its
   row's height and margin are held from first paint so the thread list above it never shrinks when the
   control appears. Files/Library/
   System are real <a href> so a modified click still opens a new tab, which means the browser's
   own prose underline applies to them by default; that one property has to live here rather than
   in rail.js's lazily-loaded stylesheet so it is never a race against when that module mounts. */
.railnav{visibility:hidden;flex:none;height:var(--sp-8);margin:var(--sp-1) 0 0}
.app.rc:not(.peek) .railnav{margin:0 0 var(--sp-1)}
.rnbtn{text-decoration:none}

/* one background: rail, top bar and cards column sit on --bg; the frame under the bar is the
   single lighter surface, reaching the viewport bottom with the composer inside it. */
.main{grid-column:2;grid-row:2;display:flex;flex-direction:column;min-width:0;min-height:0;height:100%;background:var(--bg);view-transition-name:main}
.top,.comp{flex:none}
/* spans main+panel, not just .main, so the controls stay far right, panel open or not. */
/* --bar (52) holds the tallest control, the 36px usage chip, with 8px above and below it, and the bell's
   count badge inside the viewport. The
   left inset puts the title 12px inside the frame's edge; the right inset is 16px, so the badge's
   outer edge sits as far from the window's edge as the destinations pill does from the rail's. */
.top:where(:not(.c-pop)){grid-column:2/4;grid-row:1;padding:env(safe-area-inset-top) var(--sp-4) 0 var(--sp-3);display:flex;flex-wrap:wrap;align-items:center;gap:0 var(--sp-2);font-size:var(--t-ui);color:var(--muted);background:var(--bg);min-width:0;position:relative;background:linear-gradient(var(--edge),var(--edge)) bottom/100% var(--stroke) no-repeat,var(--bg);transition:--edge var(--m-fast)}
.top:where(:not(.c-pop))[data-edge="scrolled"]{--edge:var(--hairline-2)}
/* The scroll hairline is a --stroke tall strip painted along the bar's bottom edge, so it takes no layout height and
   the first line stays 52px; --edge is a registered colour so it fades. */
@property --edge{syntax:'<color>';inherits:false;initial-value:transparent}
/* min-width:0 shrinks to an ellipsis; flex-grow:0 stops a short title stretching the row. The
   status word gives way first (the live pill already says it); the title truncates rather than
   push the chip off. */
.top .t{flex:0 1 auto;min-width:0;color:var(--ink);font-weight:var(--fw-strong);font-size:var(--t-body);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-left:var(--sp-3)}
.app.nt:not(.altroute) .top .t{visibility:hidden}
/* the status word is one short word and is never cut to a fragment: the title beside it gives way */
.top .st{flex:none;max-width:var(--status-max);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--sans);font-size:var(--t-ui)}
.gap{margin-left:auto}
/* Whole-history spend from the server (thread_spends). */
.ttot{flex:none;font-size:var(--t-meta);font-variant-numeric:tabular-nums;color:var(--faint);text-decoration:none}
.ttot[hidden]{display:none}
/* a worded header button is the shared .btn */
.top button{background:none;border:1px solid var(--hairline-2);color:var(--muted);border-radius:var(--r-2);padding:0 var(--sp-3);height:var(--ctl);cursor:pointer;font-size:var(--t-ui);transition:color var(--m-fast),border-color var(--m-fast)}
.top button:hover{color:var(--ink);border-color:var(--hairline-3)}
.top button:active{background:var(--surface-2)}
.top .ib,.top .vbtn{border:0}
.top #menu{display:none;border:0;padding:0;width:var(--ctl);height:var(--ctl);flex:none}
.top #hzmore{display:none;border:0;flex:none}
/* --row (36) holds a three-digit ring (32px) uncut; the bare chip is one control high. */
.top #usage{height:var(--row);flex:none}
.top #usage.bare{height:var(--ctl)}
/* The destinations line (ui/modules/nav.js folds and wires it) opens .top on every page, beside
   usage, the mic and the bell. A thread's own title, state, view choice and panel toggle take a
   second line under it, so a thread never squeezes the links; Home and a destination page have none.
   ::after is the line break: alone on its own zero-height line, it sends everything ordered after it
   to the next one. Icons, the current page's mark and the waiting counts are nav.js's. */
.top:where(:not(.c-pop))::after{content:"";order:1;flex-basis:100%}
.top :is(.t,.st,.ttot,.gap,#handto,#hzmenu){order:2}
/* A zero basis, so a long title truncates rather than breaking its line, grown back to no more than
   its own text, so the state word still follows it. */
.top .t{flex:1 1 0;max-width:max-content;line-height:var(--sp-10)}
/* .hnav is the free room on the line; .hpill is the links' own container, the usage chip's shape (the
   pill radius, --surface-2 fill, no border, one --row high) grown to hold them. */
nav.hnav{flex:1 1 0;min-width:0;height:var(--bar);display:flex;flex-direction:row;gap:0;align-items:center;overflow:hidden;margin-left:var(--sp-1);container-type:inline-size}
.hpill{flex:0 1 auto;min-width:0;height:var(--row);display:flex;align-items:center;gap:2px;padding:0 var(--sp-1);border-radius:var(--r-pill);background:var(--surface-2)}
.hnav a,.hnpop a,#hnmore{flex:none;display:flex;align-items:center;gap:var(--sp-1);height:var(--ctl);padding:0 calc(var(--sp-2) + 2px);border:0;border-radius:var(--r-pill);background:none;color:var(--muted);font:var(--t-ui) var(--sans);text-decoration:none;white-space:nowrap}
.hnav a,.hnpop a,.hnmore>button{transition:color var(--m-fast),background var(--m-fast)}
.hnmore[hidden]{display:none}
/* Where the pill shows, it is centred in the free room, with token gaps and padding. */
@media(min-width:761px){.hnav{justify-content:center}.hpill{gap:var(--sp-1)}.hnav a,.hnpop a,#hnmore{padding:0 var(--sp-3)}}
@media(min-width:761px){.app:is(.nt,.altroute) .top :is(.t,.st,.gap){display:none}}
/* the voice control mounts after the first screen; its slot is held at the button size from the first paint so the bar never shifts when the mic arrives */
@media(min-width:761px){#voiceCtl:empty{width:var(--ctl);height:var(--ctl);flex:none}}
/* The Plain/Detailed/Terminal choice (panel.js hides it off-thread); #view[hidden] beats the segmented component's display. */
/* 2px: the track's hairline inset around its segments */
.seg{display:inline-flex;align-items:center;gap:2px;padding:2px;border-radius:var(--r-pill);background:var(--surface-3);white-space:nowrap}
/* 24 tall inside the 2px track: the whole control is --ctl high, like every header control */
.seg button{border:0;height:var(--sp-6);padding:0 var(--sp-3);border-radius:var(--r-pill);background:none;font-size:var(--t-meta);line-height:var(--lh-one);color:var(--faint);cursor:pointer}
.seg button:hover{color:var(--muted)}
#handto[hidden]{display:none}
.frame{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;margin:0 var(--sp-3);background:var(--surface-2);border:1px solid var(--hairline-2);border-bottom:0;border-radius:var(--r-4) var(--r-4) 0 0;overflow:hidden;container-type:inline-size}
/* a status sentence, so sans; amber is the warn hue, not the model concept hue. It sits on the
   frame's own left and right edges, 8px above the frame. */
.reconn{flex:none;margin:0 var(--sp-3) var(--sp-2);border-radius:var(--r-3);padding:var(--sp-2) var(--sp-4);background:color-mix(in srgb,var(--warn) 16%,var(--bg));color:var(--warn-text);font-size:var(--t-meta);text-align:center}
.reconn[hidden]{display:none}
/* The library toast (components/toast.js) sits just above the composer; while one shows, the jump-to-live control rides above it. */
body.toasting .livejump{bottom:calc(var(--sp-3) + var(--sp-8) + var(--sp-2))}
#loaderr{background:color-mix(in srgb,var(--error) 14%,var(--bg));color:var(--error-text)}
#loaderr .lerr{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:var(--sp-2) var(--sp-3)}
#loaderr .btn{color:inherit;border-color:currentColor}
/* an error sentence under the composer, on the composer's own text edge */
.cerr{max-width:var(--thread-col);margin:0 auto;padding:var(--sp-2) var(--sp-4) 0;background:transparent;color:var(--error);font-size:var(--t-meta)}
.cerr[hidden]{display:none}
.live{display:inline-flex;align-items:center;justify-content:center;width:var(--live-w);height:var(--live-w);flex:none}
.live i{width:var(--dot);height:var(--dot);border-radius:50%;background:var(--hairline-3);flex:none}
.live.warn i{background:var(--warn);animation:ala-pulse var(--m-pulse) infinite}
.live.down i{background:var(--error)}
.live.stale i{background:var(--stale);animation:ala-pulse var(--m-pulse) infinite}
/* A healthy connection draws no mark, and its box stays so the bar does not shift; only reconnecting, down and offline earn one. */
.live.ok i{visibility:hidden}
.stream{position:relative;flex:1;min-height:0;overflow:auto;padding:var(--sp-6);background:transparent;container-type:inline-size}
/* Every ordinary turn keeps the reading column (--thread-col: 720px up to 960px with .stream's width). The wrap itself is the centred column, so
   each turn's own margins (the bubble's spacing, the 34px gutter) apply inside it and never undo
   the centring. A compare grid (.cmpgrid) and the prompt right above one (:has) break out to
   .stream's own width, read in container units off .stream; both stay centred on that width,
   never left-anchored, since margin-left and width are computed from the same cqw origin. A
   deliverable card shares the column's left edge like prose and steps (docs/UI-DESIGN.md:48); only
   once a grid holds a second card does it earn the same centred breakout, so a lone card never
   renders pinned to the pane's edge while the message column sits inset beside it. */
.wrap{max-width:var(--thread-col);margin:0 auto;padding-bottom:var(--sp-6)}
/* No harness column to balance the rail leaves .wrap centred on .main, not the viewport, off by
   half the rail's width (.pc; the harness is never in the grid below 1100px either). The closed
   panel's shift is density.js's. */
.wrap>.cmpgrid,.wrap>.uw:has(+.cmpgrid){width:100cqw;max-width:none;margin-left:calc(50% - 50cqw)}
/* scoped to #wrap: a bare .grp selector also matches the header usage chip's own button
   (threadpanel.js's usagePaint, class="grp"), pushing it 34px right of its own pill below and,
   at a narrow width, past its own clipped edge entirely (line 697's margin-left is the one that
   actually breaks it; this margin is the same collision, just cosmetically harmless on its own). */
#wrap .grp{margin:0}
.ask button:hover,.ask button:focus-visible,.askrow .chip:hover,.askrow .chip:focus-visible{border-color:var(--warn-text);background:var(--wash-state)}
.ask.done button.picked,.askrow.answered .chip.picked{opacity:1;border-color:var(--warn-text);background:var(--wash-state)}
/* a run's header pulls its hover padding back out, so the actor tag sits on the column edge.
   Scoped to the transcript: a rail row carries .run as its running state. */
#wrap .run{margin:0 calc(-1 * var(--sp-2));border-radius:var(--r-3)}
.run .chip.thr{font-size:var(--t-meta);color:var(--agent);border-color:color-mix(in oklab,var(--agent) 45%,transparent);font-family:var(--mono);background:none}
.run .chip.thr:hover{background:color-mix(in oklab,var(--agent) 12%,transparent)}
.comp .ro{max-width:var(--thread-col);margin:0 auto;padding:var(--sp-3) 0;text-align:center;color:var(--faint);font-size:var(--t-ui)}
.app.ro .comp .box,.app.ro .comp .cerr{display:none}
.note{color:var(--faint);font-size:var(--t-meta);margin:var(--sp-2) 0}

@container (min-width:420px){.arts .dbs{grid-template-columns:repeat(2,1fr)}}
@container (min-width:640px){.arts .dbs{grid-template-columns:repeat(3,1fr)}}

/* The chat dock (ui/modules/homedock.js) draws only on blank Home, where it sets .app's data-dock. */
.dock{display:none}
/* composer: the card floats on the page background with clear space around it; the textarea spans it and one slim toolbar row sits under it.
   Everything inside shares one 8px inset; the textarea adds 8px so typed text starts 16px in. */
.comp{container-type:inline-size;padding:var(--sp-3) var(--sp-6) calc(var(--sp-5) + env(safe-area-inset-bottom));background:transparent}
.comp .box{max-width:var(--thread-col);margin:0 auto;border:1px solid var(--hairline-2);border-radius:var(--r-4);background:var(--surface-3);padding:var(--sp-2);display:flex;flex-direction:column;gap:var(--sp-1);transition:border-color var(--m-fast),box-shadow var(--m-fast)}
.comp .box:focus-within{border-color:var(--accent);box-shadow:var(--e-focus)}
/* 16px: iOS zooms the page on focus for anything smaller */
.comp .c-field-l.srt{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
.comp .c-field.bare{gap:0}
.comp textarea{width:100%;resize:none;font:var(--t-user)/1.5 var(--sans);min-height:var(--bar);max-height:var(--compose-max);padding:var(--sp-2) var(--sp-2) 0}
.comp textarea::placeholder{color:var(--faint)}
.comp .bar{display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;gap:var(--sp-1);min-height:var(--row)}
/* attach and dictate are library ic buttons; the bar places them */
.comp .bar .c-field-file{display:contents}.comp .bar .c-field{display:contents}
.comp .att,.comp .mic{align-self:center;flex:none}
/* push-to-talk: held down it turns the record red with a pulsing dot; released it goes faint
   while the clip transcribes, so the same button carries both states with no extra chrome */
.comp .mic{position:relative}.micg{display:none}
.comp .mic[aria-pressed=true]{color:var(--error)}
.comp .mic[aria-pressed=true]::after{content:'';position:absolute;top:var(--sp-1);right:var(--sp-1);width:var(--dot);height:var(--dot);border-radius:50%;background:var(--error);animation:ala-pulse var(--d-pulse) var(--ease-breathe) infinite}
.comp .mic.busy{color:var(--faint);cursor:progress}
/* the primary action: a filled circle in the neutral ink/bg pair (light on dark theme, dark on
   light theme), never a hue, so it reads as one control whether it means send or stop; the arrow
   morphs into a small square in place rather than swapping icons */
.comp .send{position:relative;margin-left:auto;align-self:center;flex:none}
.comp .send:active{transform:scale(.93)}
.comp .send:disabled{opacity:1;cursor:not-allowed}
.comp .send svg{width:var(--icon-sm);height:var(--icon-sm);stroke:currentColor;fill:none;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;transition:opacity var(--m-fast),transform var(--m-fast)}
.comp .send .sq{position:absolute;top:50%;left:50%;width:var(--sp-3);height:var(--sp-3);margin:calc(var(--sp-3) / -2) 0 0 calc(var(--sp-3) / -2);opacity:0;transform:scale(.4);fill:currentColor;stroke:none}
.comp .send.cmpwait{opacity:var(--o-dim);pointer-events:none}

.wrap>.empty{text-align:center;margin-top:14vh;color:var(--muted);font-size:var(--t-user);line-height:var(--lh-prose)}
/* the hero mark matches the sign-in card's: 40px mark, the name at title size */
.wrap>.empty .brand{justify-content:center;border:0;height:auto;padding:0;margin-bottom:var(--sp-4);gap:var(--sp-3)}
.wrap>.empty .brand .mk{width:var(--sp-10);height:var(--sp-10);border-radius:var(--r-4)}.wrap>.empty .brand .mk::before{inset:var(--sp-1)}.wrap>.empty .brand .mk::after{inset:var(--sp-3)}
.wrap>.empty .brand .nm{font-size:var(--t-title);letter-spacing:.2em}
.wrap>.empty p{margin:0 auto var(--sp-6)}
.wrap>.empty .ex{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--sp-2)}
/* the hero's example chips are the shared .chip one step larger: the one place the page invites */
.wrap>.empty .chip{min-height:var(--row);padding:0 var(--sp-4);font-size:var(--t-body)}
.wrap>.empty .chip:hover,.wrap>.empty .chip:focus-visible{border-color:var(--accent);background:var(--accent-wash)}

/* harness panel: a standalone full-height dark surface set 20px off the chat column's border */
.harness{grid-column:3;grid-row:2;height:100%;min-height:0;overflow:auto;margin-left:0;padding:0 var(--sp-3) var(--sp-3);background:var(--bg);display:flex;flex-direction:column;gap:var(--sp-3);min-width:0}
.card{flex:none}
.card{margin:0;background:var(--surface-2);border:1px solid var(--hairline);border-radius:var(--r-4)}
/* a card header: the shared caps label in a 40px bar, the card's 12px inset */
.card h4{margin:0;height:var(--sp-10);padding:0 var(--sp-3);cursor:pointer;user-select:none;display:flex;align-items:center;gap:var(--sp-2)}
.card h4 .n{font-size:var(--t-meta);letter-spacing:0;text-transform:none;color:var(--faint);margin-left:auto;font-variant-numeric:tabular-nums}
.card h4 .n:empty{display:none}
.card h4::after{content:'';width:var(--dot);height:var(--dot);border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(45deg);margin-left:auto;transition:transform var(--m-state)}
.card h4:has(.n:not(:empty))::after{margin-left:0}
.card.closed h4::after{transform:rotate(-45deg)}
.card .cb{padding:0 var(--sp-3) var(--sp-3)}.card.closed .cb{display:none}
/* tab/library/system markup+CSS ship lazily (panel.js, library.js, system.js); .lsview/.altroute
   stay inline since mountView toggles them before either lazy import could ever resolve. The view is
   its pane's one scroll container, and on desktop it takes the right panel's column too. */
.lsview{display:none;flex:1;min-width:0;min-height:0;overflow:auto;background:var(--bg)}
.lsview.on{display:block}
/* The page head (ui/components/pagehead.js) is each view's first child and spans the same page box as
   the page under it (dash.js .dash: the page's full width, inset at its sides), so the title sits at
   one place on every page. The page under it starts at the head's bottom gap. */
.lsview>.c-phd{flex:none;width:100%;padding:var(--sp-6) var(--sp-8) var(--gap-group)}
.lsview>.c-phd~.dash{padding-top:0}
/* A view laid out as a flex row (Mail) stacks its head over its panes. */
.lsview.on:has(>.c-phd){flex-direction:column}
/* A head with no state line, its title hidden on a phone, is inset above and below by a small gap. */
@media(max-width:760px){.lsview>.c-phd{padding:var(--sp-4) var(--sp-4) var(--gap-group)}.lsview>.c-phd:not(:has(.c-phd-s)){padding:var(--sp-2) var(--sp-4) var(--gap-tight)}}
@media(min-width:1100px){.app.altroute .harness{display:none}.app.altroute .main{grid-column:2/4}}
.app.altroute .frame{display:none}
/* A bare view (ui/nav/registry.json `bare`) is a second screen kept beside the app: no rail, header, tab bar or right panel, and the view draws its own bar. */
.app.bare{grid-template-columns:minmax(0,1fr)}
.app.bare :is(.rail,.top,.harness,.tabbar,#tabbar){display:none}
.app.bare .main{grid-column:1;grid-row:1/3}
/* A page that is not a thread keeps none of the previous thread's header chrome: its cost, the
   Plain/Detailed/Terminal choice (and the phone's view menu) and the details-panel toggle. */
.app.altroute .top #ttotal,.app.altroute .top #handto,.app.altroute .top #hzmenu{display:none}
/* the usage indicator (threadpanel.js usagePaint, its look in skin.js); a click opens the usage sheet */
.usage[hidden]{display:none}
.usage{display:flex;align-items:center;gap:var(--sp-1);padding:0 var(--sp-2);border-radius:var(--r-pill);font:inherit;font-size:var(--t-meta);color:var(--faint);white-space:nowrap;background:var(--surface-2);font-variant-numeric:tabular-nums}
.skip{position:absolute;left:var(--sp-2);top:calc(-1 * var(--sp-12));z-index:var(--z-tip);padding:var(--sp-2) var(--sp-3);border-radius:var(--r-3);background:var(--surface-3);color:var(--ink);font-size:var(--t-ui);text-decoration:none}
.skip:focus{top:var(--sp-2)}
/* Below 1100px the harness panel hides from the grid but stays reachable as a right-side overlay
   behind #hzmenu, the same pattern the thread rail uses for the left overlay below 760px. Above
   it, the docked panel only has a thread's own panel to toggle, so #hzmenu shows only while a thread is open
   (panel.js clears its hidden attribute; not Home or Activity). */
#hzmenu{display:none}
#hzmenu:not([hidden]){display:grid}
/* collapsible right panel (panel.js): .pc zeroes the grid track; panelSet disables the .app transition in JS first, so the track snaps instead of animating */
/* .nt (renderHeader): the blank New thread has no panel content, so main spans the freed column and centres */
/* .act (renderHeader): the activity thread has no panel either, so it spans the column too and #hzmenu has nothing to open */
@media(min-width:1100px){.app.pc{grid-template-columns:var(--navc) 1fr 0}.app.pc .harness{display:none}.app:is(.nt,.act) .harness{display:none}.app:is(.nt,.act) .main{grid-column:2/4}}
.app:is(.nt,.act) .top #hzmenu{display:none}
@media(max-width:1099.98px){.harness{display:none}.app{grid-template-columns:var(--navc) 1fr}.main{border-right:0}.top{grid-column:2}#hzmenu:not([hidden]){display:grid}
  /* the drawer (sheet.js, panel.js) owns the status-bar inset, so the tab bar keeps only its own gap */
  .c-sheet-b>.harness{display:flex;flex:1;height:auto}}
@media(max-width:760px){.app,.app.rc{grid-template-columns:1fr}.rail{display:none}.main{grid-column:1}.stream,.comp{padding-left:var(--sp-3);padding-right:var(--sp-3)}.top{grid-column:1;padding:0;gap:0 2px}/* 2px: the phone header's 44px targets sit nearly edge to edge */
  /* A phone has one header line, the page, and the bottom tab bar (nav.js, docs/MOBILE.md) that
     replaces the destinations line; the bell stays in the header as the one way to Needs you. The
     bar is the grid's third row, so no page scrolls under it and it rides the shell's --vvh height
     above the keyboard. The page is one surface edge to edge: the frame drops its inset, edge and
     lighter fill. */
  .app{grid-template-rows:auto 1fr auto}nav.hnav{display:none}
  /* On a thread the presence portrait steps aside so the title has the room; the three controls stay. */
  .app:not(.nt):not(.altroute) #pstrip{display:none}
  .frame{margin:0;border:0;border-radius:0;background:var(--bg)}
  .top :is(.t,.st,.ttot,.gap,#handto,#hzmenu){order:0}.top :is(.live,#usage,#voiceCtl){order:1}.top #bell{order:2}.top::after{order:3}
  .top #menu{display:grid}.top .t{padding-left:0;margin-right:auto}
  /* A phone title is the one thing the bar names, so it wraps to two balanced lines rather than ending in an ellipsis;
     the bar keeps its one --bar height, centred, and only a title longer than two lines is clamped. */
  .top{min-height:var(--bar);align-content:center}
  .top .t{line-height:var(--lh-tight);white-space:normal;text-overflow:clip;text-wrap:var(--wrap-title);display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2}.top .gap{display:none}.top #status{display:none}.top .live{width:var(--sp-2)}.top #ttotal{display:none}
  /* The thread's view level, hand to and details live behind the one overflow button. */
  .app .top :is(#handto,#hzmenu){display:none}.top #hzmore:not([hidden]){display:grid;order:0}.app:not(.altroute) .top:has(#hzmore:not([hidden])) #usage{display:none}
  /* every top-bar icon button (open threads, panel details) is a real touch target on a phone,
     not just a 28px glyph -- the padding grows the hit box, the icon inside stays the same size.
     #menu carries its own id-scoped size above, so it needs its own override here too. The rail's
     New and Search rows grow to the same 44px. */
  .ib{width:var(--row-touch);height:var(--row-touch)}
  .top #menu,.top #hzmenu,.top #hzmore,.top #handto,.top #bell,.top .vbtn{width:var(--row-touch);height:var(--row-touch)}
  /* the bell is the bar's right-most button: one gap step off the mic's (or the usage pill's) 44px target on its left, so the two targets never touch, and one fill step off the screen edge on its right */
  .top #bell{margin:0 var(--sp-1) 0 var(--sp-2)}
  /* the bar has no side padding on a phone, so each bar button paints its pressed fill one step inside its 44px hit box and the right-most one keeps that fill 8px off the screen edge */
  .top #menu,.top #hzmenu,.top #hzmore,.top #handto,.top #bell{padding:var(--sp-1);background-clip:content-box}
  /* the usage indicator keeps its pill shape at every width; its touch target grows past the pill through an invisible hit area */
  .top #usage .grp{position:relative}.top #usage .grp::after{content:"";position:absolute;inset:calc((var(--row) - var(--row-touch)) / 2) 0}
  /* the usage button drops its figure on a phone and spreads over the bar's space; Home's own being is the way in to talk, so the header holds no third voice control there */
  .top .usage .grp{min-width:0;padding:0 var(--sp-2)}
  /* With no Claude account connected the pill is its dot and the account name on a phone (the state stays in the dot's name and the button's title) and the Connect pill waits for the accounts sheet, so the bar's title keeps its whole word. */
  .top #usage .unc .uws,.top #usage .ustale[data-kind=connect]{display:none}
  /* the pill and its button clip their words with overflow:clip, whose clip margin (one --sp-1 step, the pill's 36px row grown to the 44px target) keeps the invisible hit area reachable above and below the pill */
  /* the title keeps its longest word whole; a wide status (not connected, with its Connect button) gives way instead, clipping its own words */
  .top .t{min-width:min-content}.top #usage{flex:1 1 0;max-width:max-content;min-width:0;overflow:clip;overflow-clip-margin:var(--sp-1)}.top #usage .grp{flex:0 1 auto;overflow:clip;overflow-clip-margin:var(--sp-1)}.top #usage .upl{min-width:0;overflow:hidden;text-overflow:ellipsis}
  /* beside the Connect button the state word is the button's own, so the pill drops its repeat and nothing is cut mid-word */
  .top #usage:has(.ustale[data-kind=connect]) .uws{display:none}
  .app.nt:not(.altroute) .top .vbtn{display:none}
  .c-sheet-b>.rail{display:flex;flex:1;min-height:0;background:var(--bg);padding:0 var(--sp-2)}
  .c-sheet-b>.rail .brand{justify-content:flex-start;padding:0 var(--sp-2)}.c-sheet-b>.rail .nm{display:inline}.c-sheet-b>.rail .threads{display:block}.c-sheet-b>.rail .new{display:flex}
}
/* A phone composer is one field over one toolbar row: attach, dictate and Send, each a 44px target, on one
   line from 320px up; the chips sit in the options row under the field (composer.js); the 16px gutter matches the page's. */
@media(max-width:760px){
  .stream{padding-left:var(--sp-4);padding-right:var(--sp-4)}
  .comp{padding:var(--sp-2) var(--sp-4) calc(var(--sp-2) + env(safe-area-inset-bottom))}
  .comp .box{padding:var(--sp-1);gap:0}
  .comp textarea{min-height:var(--row-touch);max-height:calc(4lh + var(--row-touch));padding:calc((var(--row-touch) - 1lh) / 2) var(--sp-3)}
  /* The voice dock joins the row on a thread, so five 44px targets and the model label share it:
     the gaps hold 8px down to 340px and close to 4px at 320px, where the label also takes two lines. */
  .comp .bar{flex-wrap:nowrap;gap:clamp(var(--sp-1), (100vw - 10 * var(--sp-8)) / 5 + var(--sp-1), var(--sp-2))}
  #mic [data-icon]{display:none}
  #mic .micg{display:block}
}
/* scoped to #wrap: every one of these is a stream turn-row class (step, fold group, note,
   turn-outcome banner, artifacts, phase divider), indented under the avatar column at .p's own
   32px. Unscoped, .grp and .done also match the header usage chip's button and turnresult.js's
   done element, which do not live in the stream and must not carry this indent. */
#wrap .s,#wrap .grp,#wrap .note,#wrap .done:not(.ask),#wrap .arts,#wrap .ph{margin-left:var(--gutter)}

/* no card body grows past a quarter of the viewport; the rest scrolls under the subtle bar */
.card .cb{max-height:26vh;overflow:auto}
/* session and browser view: .term's and .brw's rules ship with ui/modules/liveview.js, the only
   thing that ever paints either */
.stream,.harness,.card .cb,.threads{overscroll-behavior:contain}

/* Skeletons. A region with no data yet holds blocks in the shape of the rows that will land in
   it, at the same heights and margins, so no panel is ever blank and nothing moves when the
   data arrives. The block is replaced, never layered over. */
/* the block is a step off whatever surface it sits on, so it reads on the page background and
   inside a card, in both themes */
.sk{position:relative;overflow:hidden;border-radius:var(--r-1);background:color-mix(in oklab,var(--ink) 8%,transparent);flex:none}
.sk::after{content:'';position:absolute;inset:0;transform:translateX(-100%);background:linear-gradient(90deg,transparent,color-mix(in oklab,var(--ink) 10%,transparent),transparent);animation:ala-shimmer var(--m-shimmer) infinite}
/* rail rows: 32px pitch, each block on the rail's text edge */
/* inside an agent's own region (data-cast, ui/components/cast.js) the block, and the empty state's mark, take
   the agent's colour, so loading and empty read as that agent */
[data-cast] .sk{background:var(--cast-tint)}
[data-cast] .dstate.empty svg{color:var(--cast)}
.skrail{padding:0 0 var(--sp-2)}
/* sibling margins collapse, so 16 above and below a 16px block is the 32px row pitch */
.skrail .sk{height:var(--sp-4);width:62%;margin:var(--sp-4) var(--sp-2) var(--sp-4) var(--sp-8)}
.skrail .sk:nth-child(3n+2){width:74%}
.skrail .sk:nth-child(3n){width:46%}
/* a group heading bar sits on the label's own edge, above its three rows, as the loaded list draws them */
.skrail .skh{height:var(--sp-3);width:34%;margin:var(--sp-4) var(--sp-2) var(--sp-2) var(--sp-4)}
.live.off i{background:var(--faint)}
/* The server serves a newer build than this page runs and something is in flight, so the page has
   not reloaded itself yet (swupdate.js): one quiet chip in the top bar, a tap reloads now. */
.top .dbtn.upd{flex:none;color:var(--mcp)}
.top .dbtn.bellb svg{width:var(--icon);height:var(--icon)}
.dbtn.livejump:not([hidden]){display:flex}.dbtn.livejump:active{transform:translateX(-50%) scale(var(--press,.98))}
/* The phone header has no room left on its row, so the chip floats just under it instead of
   pushing the bell onto a second line. */
@media(max-width:760px){.top .dbtn.upd{position:absolute;top:calc(100% + var(--sp-2));left:50%;transform:translateX(-50%);z-index:var(--z-float);box-shadow:var(--e-1)}}
.top .dbtn.upd:hover,.top .dbtn.upd:focus-visible{color:var(--ink)}


.li{display:flex;align-items:center;gap:var(--sp-2);min-height:var(--sp-8);padding:0 var(--sp-2);border-radius:var(--r-2);color:var(--muted);font-size:var(--t-ui);text-decoration:none}
.li:hover{background:var(--surface-3);color:var(--ink)}
@media(max-width:760px){
  .li{min-height:var(--row-touch)}
}
/* The phone's shared parts (docs/MOBILE.md). A page on a phone has one 16px gutter; .prow is a list
   row (a 48px minimum, a 16px title line and one muted 13px sub-line, a trailing value or control);
   .prow.go is one that opens something and ends in a chevron; .pact holds the page's one primary
   action, stuck to the bottom of the page's scroll above the tab bar, where the thumb is. */
.prow{display:flex;align-items:center;gap:var(--sp-3);min-height:var(--sp-12);padding:var(--sp-2) 0;color:var(--ink);text-decoration:none;-webkit-tap-highlight-color:transparent}
.prow>.prx{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.prow .prt{font:var(--t-user)/var(--lh-tight) var(--sans);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.prow .prs{font:var(--t-ui)/var(--lh-ui) var(--sans);color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.prow.go::after{content:"";flex:none;width:var(--sp-2);height:var(--sp-2);margin-right:var(--sp-1);border:solid var(--faint);border-width:0 2px 2px 0;transform:rotate(-45deg)}
.prow:active{background:var(--surface-2)}
.pact{position:sticky;bottom:0;z-index:var(--z-raised);display:flex;gap:var(--sp-2);padding:var(--sp-3) 0;background:linear-gradient(to top,var(--bg) 70%,transparent)}
.pact>*{flex:1;min-height:var(--row-touch)}
/* In the phone's threads drawer the mark that collapses the desktop rail is a full touch target too. */
@media(max-width:760px){.c-sheet-b>.rail .brandbtn{min-height:var(--row-touch)}}
.app.rc:not(.peek) .th.on .ic{color:var(--ink);background:var(--surface-3)}
.app.rc:not(.peek) .th.pin .ic{box-shadow:inset 0 0 0 1px color-mix(in oklab,var(--mcp) 45%,transparent)}
/* The badge sits inside the tile's corner, so the rail never scrolls sideways or cuts it; the 2px ring
   cuts it out of the tile. The list row's nudge toward an agent's avatar does not apply here, and the
   failed diamond's turned box is wider, so it sits further in. */
.app.rc:not(.peek) .th b{position:absolute;right:calc(var(--sp-1) / 2);bottom:calc(var(--sp-1) / 2);left:auto;top:auto;transform:none;width:var(--dot);height:var(--dot);margin:0;border:2px solid var(--bg);background-clip:border-box}
.app.rc:not(.peek) .th.run b{background:var(--run)}
.app.rc:not(.peek) .th.needs b{background:var(--model);background-clip:content-box}
.app.rc:not(.peek) .th.failed b{right:var(--sp-1);bottom:var(--sp-1);background:var(--error);transform:rotate(45deg)}
/* only needs-you, failed and running carry a badge; a finished or idle thread draws none */
.app.rc:not(.peek) .th:not(.run):not(.needs):not(.failed) b{display:none}
/* The heading is a flex row, not a bare label, so the "Needs you" group can carry its own count
   beside the text -- the aggregate of every open ask plus every needs-you thread, the one number
   that used to live on the pinned Home tile before Josh had it removed (23 Sep 2026: Claude and
   ChatGPT have no Home). */
/* the gap above a group is margin, so the label itself is balanced 4 over 4 */
.rlbl{display:flex;align-items:center;gap:var(--sp-2);margin-top:var(--sp-3);padding:var(--sp-1) var(--sp-2)}
.rlbl .un{flex:none;color:var(--faint);font:var(--fw-strong) var(--t-caps) var(--sans);letter-spacing:normal;font-variant-numeric:tabular-nums}
.th{min-height:var(--sp-8);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-2);cursor:pointer;color:var(--muted);display:flex;gap:var(--sp-2);align-items:center;font-size:var(--t-body);transition:background var(--m-fast);position:relative}
.th:hover{background:var(--surface-2)}.th.on{background:var(--surface-2);color:var(--ink)}
.th.pin{color:var(--ink)}
.th .pinmark{flex:none;width:var(--sp-3);height:var(--sp-3);color:var(--mcp)}
.th .pinmark svg{width:100%;height:100%;fill:currentColor;stroke:none}
/* The rail's own state mark, one per row: a dot for needs-you/running (colour alone tells them
   apart, since running also pulses), a diamond for failed, so a
   row's state reads by silhouette even where colour is hard to tell apart (AlaStateDot's own
   reasoning, shared with the native rail). A finished or idle row draws no mark; its empty
   mark box still holds the glyph column so every title keeps the rail's one text edge. */
/* The 6px mark sits centred in the 16px glyph column: a transparent 5px border pads it out and
   background-clip keeps the fill to the mark itself. */
.th b{box-sizing:content-box;width:var(--dot);height:var(--dot);border:5px solid transparent;background-clip:content-box;border-radius:50%;background-color:transparent;flex:none}
/* An agent's portrait takes the same 16px glyph column as every other row's mark, so its title sits
   on the rail's one text edge; the state mark rides the portrait's corner instead of a second column. */
.th-ava{position:absolute;left:var(--sp-2);top:50%;margin-top:calc(-.5 * var(--icon-sm));width:var(--icon-sm);height:var(--icon-sm);display:block}
.th-ava .acard-wrap,.th-ava .acard-trigger{display:block;width:100%;height:100%;padding:0;margin:0;border:0;background:none}
.th-ava .c-avatar[data-size]{--avatar-size:var(--icon-sm);--avatar-type:var(--t-caps);display:grid}
.th-ava.working .c-avatar[data-size]{box-shadow:0 0 0 var(--stroke-2) var(--accent)}
/* The phone's drawer draws no portrait, so its marks stay in the plain column. */
@media(min-width:761px){
.th:has(.th-ava) b{margin:calc(var(--sp-1) * .75);border:2px solid transparent;background-clip:border-box;transform:translate(var(--sp-1),var(--sp-1))}
.th.failed:has(.th-ava) b{transform:translate(var(--sp-1),var(--sp-1)) rotate(45deg)}
.th.run:has(.th-ava) b,.th.needs:has(.th-ava) b,.th.failed:has(.th-ava) b{border-color:var(--bg)}
}
.th.run b{background:var(--run);background-clip:content-box;animation:ala-pulse var(--m-pulse) infinite}
.th.needs b{background:var(--model);background-clip:content-box}
/* 1px and 2px are corner shapes on the 6px mark (diamond, rounded square), not the radius scale */
.th.failed b{background:var(--error);background-clip:content-box;border-radius:1px;transform:rotate(45deg)}
.th .tt{min-width:0;white-space:normal;overflow-wrap:anywhere;text-wrap:var(--wrap-prose);flex:1;outline:0}
.th .tt[contenteditable="true"]{overflow:visible;white-space:nowrap;color:var(--ink);border-bottom:1px solid var(--accent)}
/* Hidden takes no width: position:absolute (not margin-left:auto/flex:none) lifts the actions
   tray out of the row's own flex flow entirely, so .tt (flex:1) reads the row's full width
   whether or not the tray is showing, rather than a fixed 66px reserved for three invisible
   buttons (the friction sweep's own reading: about 16 characters before this, about 30 after).
   It overlays the row on hover/focus, on its own small surface so the truncated title fades
   under an edge rather than being cut by an opaque button. */
/* padding 2px: the tray's hairline inset around its buttons */
.th .acts{position:absolute;right:var(--sp-1);top:50%;transform:translateY(-50%);display:flex;align-items:center;gap:0;opacity:0;pointer-events:none;background:var(--surface-3);border-radius:var(--r-2);padding:2px;box-shadow:calc(-1 * var(--sp-3)) 0 var(--sp-2) calc(-1 * var(--sp-1)) var(--surface-3)}
/* :hover is gated to a real mouse: a touch device's own occasional "sticky hover" after a tap
   (iOS/Android both do this) would otherwise reveal the tray from a plain tap the same way the
   friction sweep's own reserved-width bug did, just through a different mechanism. Keyboard
   focus and an armed delete both stay ungated -- neither is a pointer concern. */
@media(hover:hover) and (pointer:fine){.th:hover .acts,.th:hover .resume{opacity:1;pointer-events:auto}}
.th:focus-within .acts,.th:focus-within .resume,.th .acts:has(.armed){opacity:1;pointer-events:auto}
.th .acts button,.th .resume{width:var(--sp-6);height:var(--sp-6);display:grid;place-items:center;border:0;background:none;color:var(--faint);border-radius:var(--r-1);cursor:pointer;padding:0;transition:background var(--m-fast),color var(--m-fast)}
.th .acts button:hover,.th .resume:hover,.th .resume:focus-visible{color:var(--ink);background:var(--surface-3)}
.th .acts svg,.th .resume svg{width:var(--icon-sm);height:var(--icon-sm);stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.th .acts button.on svg{fill:currentColor;stroke:none;color:var(--mcp)}
.th .navic{width:var(--icon-sm);height:var(--icon-sm);stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none}
/* collapsed rail: the glyph is the title's own first letter, hidden in the full row where .tt
   already names the thread; shown only once the row shrinks to an icon. */
.th .ic{display:none}
.replaying{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.replaying[hidden]{display:none}
/* Shown only once the reader has scrolled away from the live edge and a turn has written past
   the fold; a border would separate it from the transcript it floats over, so it reads by its
   own raised fill instead. Cleared by reaching the foot again or by the click itself. */
.livejump{position:sticky;left:50%;bottom:var(--sp-3);transform:translateX(-50%);width:max-content;display:flex;align-items:center;gap:var(--sp-2);height:var(--ctl);padding:0 var(--sp-4);border:0;border-radius:var(--r-pill);background:var(--surface-3);color:var(--ink);font:var(--t-ui) var(--sans);cursor:pointer;box-shadow:var(--e-1);z-index:var(--z-raised)}
.livejump[hidden]{display:none}
.livejump i{width:var(--dot);height:var(--dot);border-radius:50%;background:var(--run)}
/* the turn a person typed is a right aligned tinted bubble; Ala answers left as plain prose, so the
   two never read the same at a glance */
.wrap>.uw{display:flex;flex-direction:column;align-items:flex-end;margin:var(--sp-4) 0 var(--sp-3)}
.u{max-width:72%;margin:0;padding:var(--sp-2) var(--sp-4);border-radius:var(--r-4);background:color-mix(in oklab,var(--accent) 12%,var(--surface-2));font-size:var(--t-user);line-height:var(--lh-ui);color:var(--ink);white-space:pre-wrap;font-weight:var(--fw-medium)}
.p{margin:var(--sp-2) 0;font-size:var(--t-prose);line-height:var(--lh-prose);color:var(--ink)}.p p{margin:0 0 var(--sp-3)}
.p pre{background:var(--surface-3);border:1px solid var(--hairline-2);padding:var(--sp-3) var(--sp-4);border-radius:var(--r-3);overflow:auto;font-size:var(--t-ui);line-height:var(--lh-ui)}
/* 1px above and below inline code is an optical nudge, so its fill clears the letters without
   opening the line */
.p code{font-family:var(--mono);font-size:var(--t-body);background:var(--surface-3);padding:1px var(--sp-1);border-radius:var(--r-1)}.p pre code{background:none;padding:0;font-size:inherit}
.p table{border-collapse:collapse;margin:var(--sp-3) 0}
.p th,.p td{padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--hairline-2)}
.p th{text-align:left;font-weight:var(--fw-strong);color:var(--muted)}
.p td.num,.p th.num{text-align:right;font-variant-numeric:tabular-nums}
@media(max-width:760px){.p pre{box-shadow:inset calc(var(--sp-4) * -1) 0 var(--sp-3) calc(var(--sp-3) * -1) var(--scrim-soft)}}
.p h1,.p h2,.p h3{font-size:var(--t-user);line-height:var(--lh-ui);margin:var(--sp-6) 0 var(--sp-2);color:var(--ink);font-weight:var(--fw-strong)}.p h1{font-size:var(--t-title)}
.p ul,.p ol{padding-left:var(--sp-6);margin:var(--sp-2) 0}.p li{margin:var(--sp-1) 0}.p a{color:var(--ink);text-decoration:underline;text-underline-offset:2px}
.p strong{font-weight:var(--fw-strong)}
.p .srcs{display:flex;flex-wrap:wrap;gap:var(--sp-2);margin-top:var(--sp-3)}
.p .srcchip{position:relative;display:inline-block}
.p .srcchip>button{font-family:var(--mono);background:var(--surface-2);cursor:pointer}
.p .srcchip>button:hover,.p .srcchip>button:focus-visible{background:color-mix(in srgb,var(--tool) 12%,var(--surface-2))}

/* A concept pill sits inside a 13px sentence: mono one size down, a 20px box, the inline-code radius. */
.pill{display:inline-block;padding:0 var(--sp-2);border-radius:var(--r-1);font-family:var(--mono);font-size:var(--t-meta);line-height:var(--sp-5);border:1px solid;vertical-align:1px;white-space:nowrap}
.pill.skill{color:var(--skill);border-color:color-mix(in srgb,var(--skill) 45%,transparent)}
.pill.tool{color:var(--tool);border-color:color-mix(in srgb,var(--tool) 45%,transparent)}
.pill.model{color:var(--model);border-color:color-mix(in srgb,var(--model) 45%,transparent)}
.pill.mcp{color:var(--mcp);border-color:color-mix(in srgb,var(--mcp) 45%,transparent)}
.pill.memory{color:var(--memory);border-color:color-mix(in srgb,var(--memory) 45%,transparent)}
.pill.agent{color:var(--agent);border-color:color-mix(in srgb,var(--agent) 45%,transparent)}
.pill.plan{color:var(--plan);border-color:color-mix(in srgb,var(--plan) 45%,transparent)}
/* Activity's actor tag: neutral, never .pill.agent's red (that read as an error). */
.pill.actor{color:var(--muted);border-color:transparent;background:var(--surface-3)}
/* A finished turn's summary pills are phrases ("read 2 files"), so they set in sans; mono stays for identifiers. */
.pillrow .pill{font-family:var(--sans)}

/* step lines: one human sentence each, state at the right edge. A 20px line plus 4px above and
   below makes every step row --ctl (28) high; the state mark sits in the turn's 16px glyph column
   at the start of the 32px gutter, under the answer's own mark. */
.s{position:relative;display:flex;gap:var(--sp-2);align-items:baseline;padding:var(--sp-1) 0;margin:0;border:0;background:none;font-size:var(--t-ui);color:var(--muted);cursor:default;line-height:var(--sp-5)}
.s .l{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
.s.err .l{color:var(--error)}
.s .r{color:var(--faint);flex:none;font-size:var(--t-meta);font-variant-numeric:tabular-nums;text-align:right}
.s .m{position:absolute;left:calc(-1 * var(--sp-8));top:var(--sp-1);width:var(--icon-sm);text-align:center;color:var(--faint);flex:none;font-size:var(--t-meta);line-height:var(--sp-5)}
.s.err .m{color:var(--error)}
/* the 6px dot centred in the 16px column: a transparent 5px border pads it, the fill stays
   inside; 2px drops the 16px box onto the 20px line's centre */
.s.pending .m{box-sizing:content-box;top:calc(var(--sp-1) + 2px);line-height:var(--lh-one);height:var(--dot);width:var(--dot);border:5px solid transparent;background-clip:content-box;border-radius:50%;background-color:var(--accent);animation:ala-pulse var(--m-pulse) infinite}
/* a step whose result never arrived: no spinner, dimmed, its own line says so instead of a duration */
.s.unresolved{opacity:var(--o-soft)}
.s.unresolved .r{font-style:italic}
.s.unresolved.incomplete .l,.s.unresolved.incomplete .m{color:var(--warn-text)}
.s .l a{color:var(--memory);text-decoration:underline dotted;text-underline-offset:3px}.s .l a:hover{text-decoration-style:solid}
.s.open .l{white-space:pre-wrap;word-break:break-word}
.grp .gh{cursor:pointer;user-select:none}.grp .gh:hover{border-color:var(--hairline-3)}
.grp .gh .m{display:inline-block;transition:transform var(--m-state)}.grp.open .gh .m{transform:rotate(90deg)}
.grp .gb{display:none;padding-left:var(--sp-4)}.grp.open .gb{display:block}
/* The levels' own rules live in ui/modules/density.js: this sheet is at its byte ceiling. */
.grp.turnfold.ask .gh .l,.grp.turnfold.waiting .gh .l{color:var(--warn-text)}

/* Activity/Needs-you keep one bordered .ask panel per decision; chat questions use .askgrp/.askrow. A turn's
   step line that asked (.grp.turnfold.ask) stays a one-line step, never a panel. */
.ask:not(.turnfold),.askgrp{background:var(--surface-3)}
.ask:not(.turnfold){margin:var(--sp-3) 0;padding:var(--sp-3) var(--sp-4);border:1px solid var(--warn-text);border-radius:var(--r-4)}
.ask .q{font-size:var(--t-body);font-weight:var(--fw-medium);line-height:var(--lh-ui);margin-bottom:var(--sp-2);color:var(--ink)}
.ask .opts{display:flex;flex-wrap:wrap;gap:var(--sp-2)}
.ask .q.qclamp{display:-webkit-box;-webkit-line-clamp:5;-webkit-box-orient:vertical;overflow:hidden}
.ask .qmore{display:block;margin:0 0 var(--sp-2);padding:0;border:0;background:none;font:var(--t-meta)/var(--lh-ui) var(--sans);color:var(--accent);cursor:pointer;text-decoration:underline}
.ask button,.askrow .chip{border:1px solid var(--hairline-2);background:var(--surface-2);color:var(--ink);cursor:pointer}
/* Options are chips on one line where they fit: a decision is a choice between short labels,
   and stacking them full width made two words occupy a phone screen. They take the shared
   .chip size (see the shared parts at the top). */
.ask button{margin:0}
.ask.done{display:block;font-family:inherit;font-size:inherit;color:inherit;font-variant-numeric:tabular-nums}
/* An answered card keeps the UI face and its own inset: the generic .done rule below is the turn's stats line. Like an
   answered chat question it shows only the pick. */
.ask.done button{pointer-events:none}.ask.done button:not(.picked){display:none}
.askgrp{margin:var(--sp-3) 0;padding:var(--sp-3) var(--sp-4);border-radius:var(--r-4)}
.askgrp .ah{font-size:var(--t-meta);color:var(--faint);margin-bottom:var(--sp-2)}
.askrow{align-items:baseline;padding:var(--sp-1) 0}
.askrow .q{flex:1 1 100%;font-size:var(--t-body);color:var(--ink);line-height:var(--lh-ui)}
.askrow.busy .chip,.askrow.answered .chip{pointer-events:none;opacity:var(--o-dim)}
.askrow.busy .chip{opacity:var(--o-quiet)}
/* An answered question collapses to its own pick, not a row of dimmed options nobody chose. */
.askrow.answered .chip:not(.picked){display:none}
.ask .aerr,.askrow .aerr{flex:1 1 100%;color:var(--error);font-size:var(--t-meta)}
/* The ask card's own state mark (pending carries none) and its answered line, both shared by the
   activity thread's inline asks and Home's needs-you cards (threadpanel.js's homeAskCard). */
.askmark{margin-left:var(--sp-2);font-weight:var(--fw-bold)}
.askmark.submitted{color:var(--run)}
.askmark.error{color:var(--error)}
.askmark.submitting{display:inline-block;width:var(--sp-2);height:var(--sp-2);border-radius:50%;background:var(--faint);vertical-align:middle;animation:ala-pulse var(--m-pulse) infinite}
.ask button:disabled{opacity:var(--o-quiet);cursor:default}
.askdone{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-1) var(--sp-3);border-radius:var(--r-pill);background:var(--accent-wash);color:var(--ink);opacity:var(--o-soft)}
/* Radar tab: .raditem ships with ui/modules/radar.js, the only thing that ever renders one.
   Hidden by ala.radar.home_card returning null, so a quiet day costs nothing to look at. */
/* activity thread: a filter row above the stream, one folded line per run, a read only composer */
/* the filters share the transcript's column, so they start on the runs' own edge */
.afilt{display:flex;gap:var(--sp-2);width:100%;max-width:calc(var(--thread-col) + 2 * var(--sp-6));margin:0 auto;padding:var(--sp-4) var(--sp-6);flex-wrap:wrap}
.afilt[hidden]{display:none}
/* filter chips: the shared .chip size, unfilled until picked */
.afilt button{background:none;color:var(--muted)}
.afilt button:hover{color:var(--ink);border-color:var(--hairline-3)}
.afilt button.on{color:var(--agent);border-color:color-mix(in oklab,var(--agent) 55%,transparent);background:color-mix(in oklab,var(--agent) 10%,transparent)}
.afilt button .c{min-width:var(--sp-4);height:var(--sp-4);padding:0 var(--sp-1);border-radius:var(--r-pill);background:var(--mcp);color:var(--bg);font:var(--fw-strong) var(--t-caps)/var(--sp-4) var(--sans);text-align:center;display:inline-block;font-variant-numeric:tabular-nums}
.afilt button .c:empty{display:none}
/* Space between runs, and one left edge for the actor, so the eye can find where each report
   begins. Nothing is truncated: the separation is what was missing, not the words. */
#wrap .run + .run{margin-top:var(--sp-2)}
.run .rh .pill.actor{width:calc(var(--sp-8) * 2 + var(--sp-3));text-align:center;overflow:hidden;text-overflow:ellipsis;flex:none}
.run .rh{display:flex;gap:var(--sp-2);align-items:baseline;padding:var(--sp-2);font-size:var(--t-ui);color:var(--muted);text-decoration:none;line-height:var(--sp-5)}
.run.fold .rh{cursor:pointer;user-select:none}
.run.fold .rh:hover{background:var(--surface-2)}
.run .rh .l{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;color:var(--ink)}
.run .rh .r,.run .ae .r{color:var(--faint);flex:none;font-size:var(--t-meta);font-variant-numeric:tabular-nums}
.run .rh .m{flex:none;color:var(--faint);font-size:var(--t-caps);transition:transform var(--m-state)}
.run.open .rh .m{transform:rotate(90deg)}
.run .rb{display:none;padding:0 var(--sp-2) var(--sp-2) var(--sp-6)}
.run.open .rb,.run:not(.fold) .rb{display:block}
.run .ae{display:flex;gap:var(--sp-2);align-items:baseline;padding:var(--sp-1) 0;font-size:var(--t-ui);color:var(--muted);line-height:var(--sp-5)}
.run .ae .l{flex:1;min-width:0;word-break:break-word}
.run .ae.dg .l{color:var(--ink)}
.run .ae.dc .l{color:var(--agent)}
.aempty{color:var(--faint);font-size:var(--t-ui);padding:var(--sp-2) 0}
.done{display:flex;flex-wrap:wrap;gap:var(--sp-3);color:var(--faint);font-size:var(--t-meta);font-family:var(--mono);font-variant-numeric:tabular-nums;margin:var(--sp-4) 0 var(--sp-2);padding:0;border:0}
.done.err{color:var(--error)}
/* a card grid sized off its own container width, not the viewport: 1 column narrow (a phone
   or the rail), 2 at the cards column's usual width, 3 once the deliverables view is wide */
.arts{margin:var(--sp-3) 0 0;container-type:inline-size}
.arts .dbs{display:grid;grid-template-columns:1fr;gap:var(--sp-2);margin:0 0 var(--sp-3)}
/* a produced file is a compact row card: 40px thumbnail or type glyph, kind tag, one-line
   title and subtitle, both truncated with an ellipsis; hover reveals its open actions */
.art{position:relative;display:flex;align-items:center;gap:var(--sp-3);min-width:0;padding:var(--sp-2);background:var(--surface-3);border:1px solid var(--hairline-2);border-radius:var(--r-4);color:var(--ink);text-decoration:none;transition:border-color var(--m-fast)}
.art:hover,.art:focus-within{border-color:var(--accent)}
.art .tb{width:var(--sp-10);height:var(--sp-10);flex:none;border-radius:var(--r-3);background:var(--surface-2);border:1px solid var(--hairline);display:grid;place-items:center;overflow:hidden;color:var(--faint)}
.art .tb img{width:100%;height:100%;object-fit:cover;display:block}
.art .tb svg{width:var(--icon);height:var(--icon);stroke:currentColor;fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.art .mt{min-width:0;flex:1;display:flex;flex-direction:column;gap:2px} /* 2px between a title and its sub-line */
.art .k{font-size:var(--t-meta);color:var(--faint)}.art .k::first-letter{text-transform:uppercase} /* the kind reads as a word: Page, Document */
.art .t{font-size:var(--t-ui);font-weight:var(--fw-strong);color:var(--ink);line-height:var(--lh-tight);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.art .d{font-size:var(--t-meta);color:var(--faint);line-height:var(--lh-tight);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.art .ac{display:flex;flex:none;gap:var(--sp-1);opacity:0;pointer-events:none;transition:opacity var(--m-fast)}
.art:hover .ac,.art:focus-within .ac{opacity:1;pointer-events:auto}
/* With a pointer that hovers, the actions float over the card's end, so a title has the whole row until then. */
@media(hover:hover) and (min-width:761px){.art .ac{position:absolute;right:var(--sp-2);top:50%;transform:translateY(-50%);padding-left:var(--sp-2);background:var(--surface-3)}}
.art .ac a{display:inline-flex;align-items:center;min-height:var(--ctl);font-size:var(--t-meta);font-family:var(--sans);color:var(--muted);background:var(--surface-2);border:1px solid var(--hairline-2);border-radius:var(--r-2);padding:0 var(--sp-2);text-decoration:none}
.art .ac a:hover{color:var(--ink);border-color:var(--accent)}
/* A touch screen has no hover to reveal the actions, so they stay drawn and take a 44px hit area. */
@media(hover:none),(max-width:760px){.art .ac{opacity:1;pointer-events:auto}.art .ac a{min-height:var(--row-touch)}}
/* On a phone the actions take their own row under the title, so a card never grows past the viewport. */
@media(max-width:760px){.art{flex-wrap:wrap}.art .mt{flex:1 1 0}.art .ac{flex:1 0 100%;flex-wrap:wrap}}
/* the compare lane chips and confirmations take their own full-width line in the options row */
#cmplanes,.cmpfable,.cmpmode{flex:1 1 100%}
/* the files a sent message carried, as chips under its bubble */
.uw .achips{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--sp-2);margin-top:var(--sp-2);max-width:72%}
.uw .achip{display:flex;align-items:center;gap:var(--sp-2);max-width:100%;border:1px solid var(--hairline-2);border-radius:var(--r-3);background:var(--surface);padding:var(--sp-1) var(--sp-3) var(--sp-1) var(--sp-1);font-size:var(--t-meta);color:var(--muted);text-decoration:none}
.uw .achip:hover{background:var(--surface-3);color:var(--ink)}
.uw .achip img{width:var(--sp-8);height:var(--sp-8);object-fit:cover;border-radius:var(--r-1);display:block}
.uw .achip .pico{display:grid;place-items:center;width:var(--sp-8);height:var(--sp-8);border-radius:var(--r-1);background:color-mix(in oklab,var(--hue) 16%,var(--surface-2));color:var(--hue);flex:none}
.uw .achip .pico svg{width:var(--sp-5);height:var(--sp-5);fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.uw .achip .nm{max-width:var(--chip-name-max);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ink)}
.uw .achip .sz{color:var(--faint);white-space:nowrap}
/* a pending attachment: a pasted or dropped image, not yet uploaded, shown above the textarea
   with an annotate action; it uploads through the same /upload route as any other file, once,
   at send time */
.pending{display:flex;flex-wrap:wrap;gap:var(--sp-2)}
.pending[hidden]{display:none}
.pending .pend{display:flex;align-items:center;gap:var(--sp-2);border:1px solid var(--hairline-2);border-radius:var(--r-3);background:var(--surface);padding:var(--sp-1) var(--sp-1) var(--sp-1) var(--sp-1);font-size:var(--t-meta);color:var(--muted)}
.pending .pend .pthumb{flex:none;width:var(--sp-8)}
.pending .pend .nm{max-width:calc(var(--sp-12) * 3 - var(--sp-1));overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pending .pend button{height:var(--sp-6);background:none;border:0;color:var(--faint);cursor:pointer;font:inherit;padding:0 var(--sp-2);border-radius:var(--r-1)}
.pending .pend button:hover{color:var(--ink);background:var(--surface-3)}
.pending .pend .rm{font-size:var(--t-user);line-height:var(--lh-one);padding:0 var(--sp-1)}
/* the annotate editor: a toolbar in the full sheet's head and a stage holding the canvas, so the markup happens at the
   image's own resolution and flattens cleanly to PNG */
.annotbar{display:flex;align-items:center;gap:var(--sp-1);flex:1;min-width:0}
.annotbar .sp{flex:1}
.annotstage{display:grid;place-items:center;min-height:100%;padding:var(--sp-4);background:var(--surface-3)}
.annotstage canvas{max-width:100%;height:auto;box-shadow:var(--e-3);cursor:crosshair;touch-action:none;border-radius:var(--r-1)}
/* the @ mention popover: opens above the box, rows with a check on the chosen one */
.mnpop{position:absolute;left:0;bottom:calc(100% + var(--sp-2));width:var(--hz-drawer-w);max-height:60vh;overflow:auto;background:var(--surface-2);border:1px solid var(--hairline-2);border-radius:var(--r-4);box-shadow:var(--e-2);padding:var(--sp-1);z-index:var(--z-pop);outline:0}
.mnpop[hidden]{display:none}
/* a group label and its rows share one text edge, 12px in */
.mnpop .g{padding:var(--sp-2) var(--sp-3) var(--sp-1);margin-top:var(--sp-2)}
.mnpop .g:first-child{margin-top:0}
.mnpop .o{display:flex;align-items:center;gap:var(--sp-3);min-height:var(--row);padding:var(--sp-1) var(--sp-3);border-radius:var(--r-2);cursor:pointer}
.mnpop .o:hover,.mnpop .o.f{background:var(--surface-3)}
/* the @ mention picker's rows: the name, then its role at the right edge */
.mnpop .o .tx{flex:1;display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;column-gap:var(--sp-3);min-width:0}
.mnpop .o .n{flex:1 1 auto;min-width:0;font-size:var(--t-body);color:var(--ink);line-height:var(--lh-ui);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.mnpop .o .d{flex:none;max-width:60%;font-size:var(--t-meta);color:var(--muted);line-height:var(--lh-ui);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.mnpop .o .ck{margin-left:auto;width:var(--icon-sm);height:var(--icon-sm);flex:none;stroke:var(--ink);fill:none;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;opacity:0}
.mnpop .o[aria-selected="true"] .ck{opacity:1}
.cmpfable:not([hidden]),.cmpmode:not([hidden]){display:flex;align-items:center;gap:var(--sp-2);font-size:var(--t-meta);padding:var(--sp-1) var(--sp-2) 0}
.cmpfable{color:var(--warn)}.cmpmode{color:var(--muted)}
/* a fenced page still streaming: a quiet placeholder, never the raw markup filling the column */
.art.building{cursor:default;opacity:var(--o-quiet)}
.art.building:hover{border-color:var(--hairline-2)}
/* .cmpgrid/.cmpcol* (the compare round's own grid) ship with ui/modules/compare.js instead of
   here: nothing outside that module ever renders one. */
.hr .st{font-size:var(--t-meta);color:var(--faint);flex:none;display:inline-flex;align-items:center;gap:var(--sp-1);font-variant-numeric:tabular-nums}
.hr .st i{display:inline-block;width:var(--dot);height:var(--dot);border-radius:50%;background:var(--faint)}
.hr .st.up i{background:var(--run)}.hr .st.starting i{background:var(--accent)}
/* Rows in a card are one --ctl (28) line each, name first. A row that lights up pulls its own
   8px padding back out with a negative margin, so its text still sits on the card header's edge. */
.hr{display:flex;align-items:center;gap:var(--sp-2);min-height:var(--ctl);padding:0 var(--sp-2);margin:0 calc(-1 * var(--sp-2));border:1px solid transparent;border-radius:var(--r-2);font-size:var(--t-ui);color:var(--muted)}
.hr .n{font-size:var(--t-meta);flex:none;font-variant-numeric:tabular-nums}.hr .d{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--t-meta);color:var(--faint)}
.hr .c{font-size:var(--t-meta);font-variant-numeric:tabular-nums;color:var(--faint);flex:none;min-width:var(--sp-4);text-align:right}.hr.on .c{color:var(--ink)}
.hr.model .n{color:var(--model)}.hr.tool .n{color:var(--tool)}.hr.mcp .n{color:var(--mcp)}.hr.lit{border-color:var(--accent);background:var(--accent-wash)}
/* deliverable rows: the title is the link to the deliverable itself, the kind tag at the right */
.dv{display:flex;align-items:center;gap:var(--sp-2);min-height:var(--ctl);padding:0;font-size:var(--t-ui);color:var(--ink);text-decoration:none}
.dv:hover .t{color:var(--accent)}
.dv .t{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dv .k{flex:none;min-width:var(--sp-10);text-align:right;font-size:var(--t-caps);color:var(--faint);font-variant-numeric:tabular-nums}
@media(max-width:760px){.dv{min-height:var(--row-touch)}}
/* The usage indicator is one pill-shaped button: the next-launch seat's small ring, its short tag, its use
   as a figure in full ink where there is room, and the muted count of the other ready seats. The rest is in the tooltip and
   the popover. */
.usage .grp{display:inline-flex;align-self:stretch;height:auto;align-items:center;min-width:0;font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer;border-radius:var(--r-pill)}
.usage .grp:hover{color:var(--ink)}
.usage .grp:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.usage .upl{display:inline-flex;align-items:center;gap:var(--sp-1);font-size:var(--t-caps);line-height:var(--lh-one);color:var(--muted)}
.usage .uta{font-weight:var(--fw-strong);color:var(--ink)}
.usage .ufg,.usage .umo{font-variant-numeric:tabular-nums}.usage .ufg{color:var(--ink)}
.usage .ring.sm{width:var(--icon);height:var(--icon);margin:0}.usage .ring.sm .rv{display:none}
/* a reading past the server's staleness window dims its ring */
.usage .uold{opacity:var(--o-quiet)}
/* an account's state dot, in the indicator and in the usage sheet's State line */
.ud{display:inline-block;width:var(--sp-2);height:var(--sp-2);border-radius:50%;flex:none;background:var(--faint)}
.ud.ready{background:var(--run)}.ud.near_limit{background:var(--warn)}.ud.limited,.ud.no_access,.ud.sign_in{background:var(--error)}
#sheet .ud{margin-right:var(--sp-1)}
/* a phone drops the use figure (the ring still draws it) but keeps a state word, which is all a ring-less dot has to say it; the button itself is the 44px target */
@media(max-width:760px){.usage .ufg:not(.uws){display:none}.top #usage{padding:0}.usage .grp{justify-content:center;flex:1;min-width:var(--row-touch);padding:0}}
/* the hero's mark shows only where the rail's own logo does not; the phone sheet's model label is drawn from data-n so the name is in the page text once */
@media(min-width:761px){.wrap>.empty .brand{display:none}}
/* A ring: arc clockwise to its percentage, colour from ringLevel; the figure sits inside at
   text size, never shrunk (.full is the three-digit size), in ink -- unmuted, unlike its label. */
.ring{position:relative;display:inline-flex;align-items:center;justify-content:center;width:var(--ctl);height:var(--ctl);margin:0 2px;flex:none;vertical-align:middle} /* 2px: a ring's optical side bearing */
.ring svg{position:absolute;inset:0;transform:rotate(-90deg)}
.ring.full{width:var(--sp-8);height:var(--sp-8)}.ring circle{fill:none;stroke-width:3}.ring .rt{stroke:var(--hairline-2)}
.ring.unk .rt{stroke-dasharray:3 3}.mv .mu{font-size:var(--t-meta);color:var(--faint);margin-left:var(--sp-2)}
.ring .ra{stroke:var(--run);stroke-linecap:round}.ring.warn .ra{stroke:var(--warn)}.ring.hot .ra{stroke:var(--error)}
.ring .rv{position:relative;font:inherit;color:var(--ink);font-variant-numeric:tabular-nums;line-height:var(--lh-one)}
.th:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.th .resume{flex:none}
/* the retry button is 24 wide on the row's 8px inset; the tray starts 4px past it */
.th:has(.resume) .acts{right:calc(var(--sp-8) + var(--sp-1))}
.app.rc:not(.peek) .th .resume{display:none}
.th .acts button.armed{width:auto;padding:0 var(--sp-2);font-size:var(--t-meta);color:var(--error)}
/* the header bell: a count badge on its corner, hidden at zero */
.bellb{position:relative}
.bellb svg{width:var(--icon);height:var(--icon)}
@media(min-width:761px){.top #bell{padding:0}}
@media(min-width:761px) and (pointer:coarse){.top #bell{width:var(--row-touch);height:var(--row-touch)}}
/* the badge overlaps the glyph's top right corner and stays inside the button's box, so it never hangs past the header's right padding or the bar's top; the bell's phone margins are in shell.css */
.bellb .bn{white-space:nowrap;position:absolute;top:calc(50% - var(--icon) / 2 - var(--sp-1) - 2px);right:0;min-width:var(--sp-4);height:var(--sp-4);padding:0 var(--sp-1);border-radius:var(--r-pill);background:var(--model);color:var(--on-accent);font:var(--fw-strong) var(--t-caps)/var(--sp-4) var(--sans);text-align:center;font-variant-numeric:tabular-nums;pointer-events:none}
.bellb .bn[hidden]{display:none}
.bellb[data-tone="ok"] svg{color:var(--run)}
.bellb[data-tone="warn"] .bn{background:var(--warn)}
.bellb[data-tone="bad"] .bn{background:var(--error);color:var(--on-error)}
/* The header's one motion: a ring that leaves a control in its --ring colour and fades (alaring in
   shell.css), looping on the voice button while it listens and played twice on the bell when its
   count rises (bell.js). The ring is the ::after's still box-shadow; only its scale and opacity
   animate, so it never repaints the bar. It rests hidden. Here, not in bell.js, so the first rise
   rings before the panel has ever opened. */
.bellb::after,.vbtn[data-state="listening"]::after{box-shadow:0 0 0 var(--stroke-2) color-mix(in oklab,var(--ring) 60%,transparent)}
.bellb::after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;opacity:0;--ring:var(--model)}
.bellb.ringing::after{animation:alaring var(--m-pulse) 2}
@media(max-width:760px){.top .bellb::after{inset:var(--sp-1)}}
html[data-motion-off] .bellb.ringing::after{animation:none}
/* The phone bar carries the bell and the voice button at 44px each, so the title (ui/index.html's
   phone .top .t) starts flush after the menu button, 14px from its glyph, and the live dot sits in
   an 8px box around its 6px dot. */
/* On the phone the bar's 2px gaps go: every 44px target already insets its fill by 4px, so the
   fills stay 8px apart, and the title keeps the width it needs beside the bell and voice button. */
@media(max-width:760px){.app>.top{gap:0}}
.needsHome.threadAsks{margin:0 auto var(--sp-2);width:100%;padding:0 var(--sp-3);text-align:left;font-size:var(--t-body)}
.askabout{color:var(--muted);text-decoration:underline;text-underline-offset:2px}.askabout:hover{color:var(--ink)}
@media(max-width:760px){.askabout{position:relative}.askabout::after{content:"";position:absolute;inset:calc((var(--sp-5) - var(--row-touch)) / 2) 0}}
.ask.nhcard{border:0}
.ask.mailask .mq,.ask.mailask .mm{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ask.mailask .mq{margin-bottom:var(--sp-1)}
.ask.mailask .mm{font-size:var(--t-meta);line-height:var(--lh-ui);color:var(--muted)}
.ask.mailask .opts{margin-top:var(--sp-2)}
.ask .opts a.chip{text-decoration:none;border:1px solid var(--hairline-2);background:var(--surface-2);color:var(--ink);cursor:pointer}
.ask .aexp{font-size:var(--t-meta);color:var(--faint);margin-top:-2px}
.usage.ustaled .grp{opacity:var(--o-disabled)}
.usage .ustale{font:inherit;background:none;border:0;padding:0;color:var(--model);cursor:pointer;text-decoration:underline;flex:none}
.stale{font-size:var(--t-meta);color:var(--faint);padding:var(--sp-2) 0}
/* the rail's empty and stale lines sit on its group labels' edge */
.threads .stale{padding:var(--sp-2)}
/* The state mark: shown only for an account state no ring can show (limited, no access, sign-in
   needed, unavailable, unknown). A glyph inside a filled pill, so it never reads by colour alone;
   its title and aria-label carry the state's own words. */
.usage .wstate{display:inline-flex;align-items:center;height:var(--icon);padding:0 var(--sp-2);border-radius:var(--r-pill);flex:none;font:var(--fw-strong) var(--t-meta)/1 var(--sans);background:color-mix(in oklab,var(--ink) 10%,transparent);color:var(--muted)}
.usage .wstate.error{background:color-mix(in oklab,var(--error) 16%,transparent);color:var(--error-text)}
/* phase divider: a skill step opens a new phase in the stream */
.ph{margin:var(--sp-4) 0 var(--sp-2);font:var(--fw-strong) var(--t-meta)/1 var(--sans);color:var(--skill)}
@media(max-width:760px){
  /* thread rows: a two-line title uses the row's full width -- a tap anywhere on it opens the
     thread (threadpanel.js's activate()). Pin/rename/delete sit behind a long press (the same
     500ms threshold ChatGPT's and Claude's own phone lists use), never a permanent second line:
     a friction sweep found a tap in the middle of a row landing on Pin instead, because those
     three 44px buttons sat there always, reserving the row's own space whether or not anyone
     meant to touch them (a phone has no hover to fade them in or out of first). */
  .th{display:grid;grid-template-columns:var(--icon-sm) 1fr;grid-template-areas:"dot title";column-gap:var(--sp-2);row-gap:2px;min-height:var(--row-touch);padding:var(--sp-1) var(--sp-2)}
  .th b{grid-area:dot;justify-self:center;align-self:center}
  .th .ic{display:none}
  .th .tt{grid-area:title;align-self:center}
  .th .pinmark{grid-area:title;justify-self:end;align-self:center}
  .th:has(.pinmark) .tt{padding-right:var(--sp-6)}
  /* 2px hairline gaps between the 44px long-press buttons */
  .th .acts{right:var(--sp-1);gap:2px;opacity:0;pointer-events:none}
  .th.pressed .acts,.th .acts:has(.armed),.th.pressed .resume{opacity:1;pointer-events:auto}
  .th .acts button,.th .resume{width:var(--row-touch);height:var(--row-touch)}
  .th .acts button[data-del]{margin-left:2px}
  .th .acts button.armed{width:auto;height:var(--row-touch)}
  /* a failed row's retry takes its own column, so the title ellipsises before it as at desktop width */
  .th:has(.resume){grid-template-columns:var(--icon-sm) 1fr var(--row-touch);grid-template-areas:"dot title resume"}
  .th .resume{grid-area:resume;align-self:center}
  /* a row in a room gives its tag its own column after the title */
  .th:has(.rt){grid-template-columns:var(--icon-sm) minmax(0,1fr) max-content;grid-template-areas:"dot title room"}
  .th:has(.rt):has(.resume){grid-template-columns:var(--icon-sm) minmax(0,1fr) max-content var(--row-touch);grid-template-areas:"dot title room resume"}
  .th .rt{grid-area:room;align-self:center;max-width:none}
  .th .rt .rtag{max-width:none}
  .th .rt .rtag-n{overflow:visible}
}
@media(max-width:760px){
  /* the picker popovers become a bottom sheet on a phone and every option row grows to a real touch target, text unclipped. */
  .mnpop{position:fixed;left:var(--sp-3);right:var(--sp-3);bottom:var(--sp-3);top:auto;width:auto;max-width:none;max-height:70vh;z-index:var(--z-sheet)}
  .mnpop .o{min-height:var(--row-touch)}
  #cmplanes,.cmpfable,.cmpmode{flex:none;width:100%}
}

.wrap>.uw .via{margin:var(--sp-1) 0 0;font:var(--t-meta)/var(--lh-ui) var(--sans);color:var(--faint)}
/* the queued message's own control: send it into the turn already running (composer.js) */
.wrap>.uw .now,.wrap>.uw .resumebtn,.wrap>.uw .dropbtn{margin:var(--sp-1) 0 0 var(--sp-2)}
.wrap>.uw .now[disabled]{color:var(--faint);cursor:default;text-decoration:none}
/* The turn's gutter is 32px: a 16px glyph column (the answer's mark, a step's state) and 16px
   of air before the text. */
.p{position:relative;padding-left:var(--gutter)}
.p::before{content:"";position:absolute;left:0;top:var(--sp-1);width:var(--icon-sm);height:var(--icon-sm);border-radius:var(--r-1);border:1px solid var(--hairline-3);background:var(--brand-gradient)}
/* 1px centres the 6px hole in the 16px mark */
.p::after{content:"";position:absolute;left:calc(var(--sp-1) + 1px);top:calc(var(--sp-2) + 1px);width:var(--dot);height:var(--dot);border-radius:50%;background:var(--bg)}
/* A step inside a fold group already sits in the group's indent: its fold line lands on the
   answer's text edge and its expanded lines only add the group body's own 16px. */
#wrap .grp .s{margin-left:0}
.pill i{font-style:normal;opacity:var(--o-disabled);font-size:var(--t-meta);margin-right:var(--sp-1);letter-spacing:.03em}
.skcard .sk{height:var(--sp-5);margin:var(--sp-1) 0}.skcard .sk.h{height:var(--sp-3);width:44%;margin:var(--sp-2) 0 var(--sp-3)}
/* The 4px padding keeps the first line's own 8px margin inside the skeleton, which together stand
   for the user turn's bubble and margins, so the loaded turn lands at the skeleton's height. A
   margin alone would collapse out through #wrap instead. */
.skst{margin:var(--sp-2) 0 0 var(--sp-8);padding-top:var(--sp-1)}
.skst .sk.pr{height:var(--sp-4);margin:var(--sp-2) 0}.skst .sk.pr.a{width:96%}.skst .sk.pr.b{width:89%}.skst .sk.pr.c{width:61%}
.skst .sk.pr.d,.skst .sk.pr.e{display:none}
@media(max-width:760px){.skst .sk.pr.d,.skst .sk.pr.e{display:block;height:var(--sp-3)}.skst .sk.pr.d{width:70%}.skst .sk.pr.e{width:35%;margin-bottom:0}}
.skst .sk.st{height:var(--sp-3);width:52%;margin:var(--sp-2) 0 var(--sp-3)}
/* The thread header card (threadhead.js) sits above the transcript, so its placeholder takes the card's height and the gap under it,
   reaching out to the column's edge past the skeleton's own indent. A thread with an ask draws the card plain (no title, since the
   title would repeat the first message): two sp-4 paddings around one meta line, about sp-12 tall. The skeleton's own sp-1 top
   padding already stands above the bar, so the bar is sp-12 less that. */
.skst .sk.th{height:calc(var(--sp-12) - var(--sp-1));margin:0 0 var(--sp-4) calc(-1 * var(--sp-8))}
.skst .skvr{margin:var(--sp-4) 0 0}.skst .skvr .sk.vh{height:var(--sp-3);width:30%;margin:0 0 var(--sp-3)}
/* the page of older history loading in above, sized like the step lines it becomes */
.skearlier{margin:0 0 var(--sp-2)}.skearlier .sk{height:var(--sp-3);width:46%;margin:var(--sp-4) 0 var(--sp-4) var(--sp-8)}
.skearlier[hidden]{display:none}