/* widgets/fleet-strip — the fleet's two card surfaces: the dark sidebar rail
   (#sidebar-fleet: working cards + queued + idle avatar row) and the Today-pane
   strip (#fleet-strip: .lr-fleet working tiles + wrap grid + idle strip).
   Extracted verbatim. Shared fleet vocabulary (.fs-av/.fs-pill/.fs-corner/
   .fs-surf/.fs-plan/.fs-card + keyframes) stays in the shell — drawers and the
   mission feed compose it too. */
/* ── SIDEBAR FLEET (Craig 7/12) ── */
/* ── SIDEBAR FLEET v2 (Craig 7/12) — 300px rail, full-size cards matching old horizontal strip ── */
/* ONE SCROLL CONTEXT (Craig 7/17 3:45p 'two scroll bars on the left'): the fleet rail no longer
   owns an inner overflow — it flows in the single .sidebar scroll. No nested scroll region, no
   double scrollbar. If the whole rail fits Craig's viewport it shows ZERO bars; if not, the one
   .sidebar bar carries everything (seats + user block reachable in that single scroll). */
/* ═══ VERTICAL COMPRESSION (Craig 8/5 7:50p, floor sidebar video: "move this up a good bit" —
   a full fleet of running lanes must fit with no scroll) ═══
   MEASURED, not eyeballed. At 1440x900 with 9 lanes running the sidebar's single scroll context
   ran 1344px against an 831px content budget (900 viewport − 69px user block) — 444px over, so
   Craig scrolled to see the seats that were actually working. Retiring the exec section (see
   render.js) gives back ~230px; the rest is this block, which takes padding out of every repeated
   element rather than shrinking any type. Nothing here changes a font-size, an avatar, or a card's
   information — a rail you cannot read is not a compressed rail, it is a smaller problem.
   The per-element trims below are small on purpose: they multiply by the number of seats, which is
   exactly the axis that overflows. tools/floor-sidebar-ledgers-qa.js asserts the no-scroll result
   at Craig's own viewport, so a future seat that breaks the budget fails a check instead of
   quietly restoring the scrollbar. */
#sidebar-fleet{padding:0 0 4px;min-height:0}
#sidebar-fleet .sf-hdr{display:flex;align-items:center;gap:6px;padding:0 16px 4px;border-bottom:1px solid rgba(255,255,255,.1);margin-bottom:4px}
#sidebar-fleet .sf-hdr-lbl{font:700 11px Inter;text-transform:uppercase;letter-spacing:.09em;color:rgba(255,255,255,.45);flex:1;min-width:0}
#sidebar-fleet .sf-hdr-lbl b{color:rgba(255,255,255,.85)}
/* ═══ THREE-SECTION RAIL (Craig 7/26 4:41p 'The Shop is perfect. And, GO.' — supersedes the
   12:29 two-section rail) ═══ EXEC TEAM · FLEET · THE SHOP, grouped by the KIND of work.
   The split still costs no new chrome: the section header is this same .sf-hdr component,
   instanced three times instead of once. Any instance that is not the first wears .sf-hdr-2 —
   a hairline above it and room to breathe — so the rosters read as blocks rather than one long
   list. The rule was always count-agnostic, which is why a third section needed nothing new
   here. Each header carries its own working count: one blended count answered none of the
   three questions the sections exist to answer. */
#sidebar-fleet .sf-hdr.sf-hdr-2{margin-top:6px;padding-top:6px;border-top:1px solid rgba(255,255,255,.09)}
/* ── THE PENNYWORTH BRAND RING (Craig 7/26 4:41p: 'Pennyworth keeps his brand ring: Pennyworth
   green with gold accent'). PW is the one seat in the rail that belongs to a DIFFERENT COMPANY,
   so it is the one seat that wears TWO tones. Drawn on the BUTTON / .fs-av, never on the <img>:
   the idle bench face carries filter:saturate(.45);opacity:.7, and a filter applies to an
   element's own box-shadow — putting the ring on the image would have dimmed the brand ring in
   exactly the state Craig looks at it most. Both states get the same ring, so PW reads as
   Pennyworth whether it is working or benched.

   SPECIAL MEANS COLOR (Craig 7/26 6:22p, screenshot: 'PW looks weird' — supersedes the 4:41p
   weights). What shipped was 1.5px gold inside a 4px band of a LIFTED green (#4d8a70), painted at
   the button's own 10px radius. Three faults, one look: the bench faces are circular portraits, so
   a 10px-radius band floated off their corners as a SQUARE OUTLINE; 4px is a halo next to siblings
   whose only rim is painted into their art; and a green lifted to 0.48 luminance beside a gold
   band reads as one bright yellow-green object. Craig's ruling — special is the COLOR, never the
   thickness, never a glow, never a square outline:
     GEOMETRY — 50%, hugging the circular art the way every sibling's own rim does, in both states.
     WEIGHT   — 2.5px total, the same order as an art rim. Gold is a 1px HAIRLINE against the face;
                the green band around it is the ring. That order is the standing 7/26 ruling: the
                green is the ring, the gold is the accent.
     COLOR    — the RULED 7/24 tokens, quoted, not a lifted approximation. The crest review sheet
                written the day of the lock (Pennyworth/Brand/mark-ladder-20260724/REVIEW-ladder.html)
                names them in one line: 'Brand #18251f · accent #c9a24b — both already suit this
                artwork (mark field #0e1913)'. #18251f is also pennyworthhq.com's --bg-raised and
                #c9a24b its --gold, unchanged since the 7/09 rebrand — so these two hexes ARE the
                brand, and the ring now spends them instead of approximating them.
                On the canopy rail this deep green reads as a dark medallion edge that gives the
                brass its line, which is the understated brass-on-green the crest itself is. The
                earlier note that a canon-dark green 'simply vanished' was true only of a 4px band
                asked to carry the identity alone; at hairline weight the two tones carry it
                together, and the token no longer has to win a brightness argument to be brand.
   The WORKING pulse is untouched and must stay untouched: .fs-av.work::after is the rail-wide
   breath every working seat wears in its own --ring, and it is state vocabulary, not brand. This
   ring stays a 2.5px hairline underneath it — the pulse restores no halo.
   SO THE BRAND RING AND THE SEAT COLOR ARE NOW TWO GREENS, deliberately, and the old note here
   claiming ONE green in three places is retired: _fleetBotColor.pennyworth (#4d8a70) is the STATE
   color — the working breath, the sub-dot, the feed chip — and its whole job is to be legible at
   1.6s of animation on a dark rail, which #18251f cannot do without deleting the working signal.
   --pw-green is the BRAND mark. Different jobs, so different values; the one-color law still binds
   the three STATE surfaces to each other (results/qa-shop-namespace.js proves feed === rail). */
#sidebar-fleet{--pw-green:#18251f;--pw-gold:#c9a24b}
#sidebar-fleet .sf-idles button.pwring,
#sidebar-fleet .fs-av.pwring{border-radius:50%;box-shadow:0 0 0 1px var(--pw-gold),0 0 0 2.5px var(--pw-green)}
/* the roster size, right-aligned — quiet, so the working count keeps the emphasis */
#sidebar-fleet .sf-hdr-n{flex:none;font:700 10px Inter;letter-spacing:.06em;color:rgba(255,255,255,.30);padding-left:8px}
/* Working/queued bot cards — same card feel as old horizontal lr-work, adapted for dark rail */
#sidebar-fleet .sf-card{position:relative;display:flex;align-items:center;gap:10px;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.18);border-radius:10px;padding:6px 12px 6px 8px;margin:3px 8px;width:calc(100% - 16px);box-sizing:border-box;cursor:pointer;text-align:left;transition:background .12s,border-color .12s}
#sidebar-fleet .sf-card:hover{background:rgba(255,255,255,.17);border-color:rgba(255,255,255,.32)}
#sidebar-fleet .sf-card .sf-txt{flex:1;min-width:0}
/* TILE IA (Craig 7/16): row 1 = name + plan pill right-aligned; row 2 = full-width task line
   (live dot + verb — client, graceful ellipsis) + compact '+N' queue chip. No WORKING badge —
   the green dot + live verb carries the state. */
#sidebar-fleet .sf-card .sf-nm{font:600 13px Inter;color:rgba(255,255,255,.95);overflow:hidden;white-space:nowrap;display:flex;align-items:center;gap:6px}
/* flex-shrink:0 — the NAME does not shrink at all (NAME-BEFORE-CHIP below). With shrink:1 it still
   lost sub-pixel fractions to flex rounding and drew an ellipsis on a 6-letter name at 41 of 42px.
   The chips' min-width floors guarantee the row fits at every rail, so nothing needs the name's
   pixels; the ellipsis stays declared as the last-resort net for a pathological seat label. */
#sidebar-fleet .sf-card .sf-nm .sf-nm-txt{flex:1 0 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#sidebar-fleet .sf-card .sf-jb{font:400 12px Inter;color:rgba(255,255,255,.6);margin-top:3px;display:flex;flex-wrap:wrap;align-items:flex-start;gap:3px 6px;min-width:0}
/* NO-TRUNCATION LAW, re-cut (Craig 7/19 5:14p — 'waitin g on Marian ne'): the law means the FULL
   subject is ACCESSIBLE (the title tooltip carries it), not that the card grows unbounded. Words
   wrap at word boundaries ONLY (break-word let the drift chip + slot grid crush this flex item to
   a few px and split words per character); the subtitle clamps at 2 lines with an ellipsis for a
   stable card height. min-width guarantees the text a readable column — when a drift chip lands,
   the right-side chips wrap under it instead of squeezing the words. */
#sidebar-fleet .sf-card .sf-jb-txt{flex:1 1 110px;min-width:110px;white-space:normal;overflow-wrap:normal;word-break:normal;line-height:1.35;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
/* the glyph slot right-anchors even when it wraps to its own line (pill grid law holds in both
   states). The auto-margin used to live on the q slot as the FIRST of a q+term pair; the q slot
   retired with the text pill (7/26 — depth moved to the face), so term is the cluster now. */
#sidebar-fleet .sf-card .sf-jb .sf-slot-term{margin-left:auto}
#sidebar-fleet .sf-card .sf-jb .sf-live-dot{margin-top:4px}
#sidebar-fleet .sf-card .sf-jb .sf-live-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--brand-leaf,#3fae52);animation:fsPillDot 1.6s ease infinite}
/* DRIFT CHROME (Craig 7/19 5:01p, supersedes the 7/17 'quiet' chip): a WORKING lane that
   stops producing output past the drift threshold wears amber — the dot flips amber (still
   pulsing: the processing/ claim is real) and a labeled 'no output Xm' chip lands on the
   task line. Diagnostic chrome, not alarm red (court-honest: red stays Craig-court). */
#sidebar-fleet .sf-card .sf-jb .sf-live-dot.drift{background:#d97706}
.sf-drift{flex:none;font:700 9px Inter;border-radius:999px;padding:2px 6px;background:rgba(217,119,6,.2);color:#f5b04c;letter-spacing:.02em;white-space:nowrap}
/* BEACON GAP (Craig 7/30 spec 3) — sibling of .sf-drift and deliberately NOT its color. Amber says
   'this lane may be stalled'; a beacon gap says the opposite half: the seat's ACTIVITY is live and
   only its self-report is quiet. Calm steel, so the glance reads 'reporting is behind', never
   'something is wrong'. CHIPLESS is the defect this replaces — Engineer's card said nothing about
   its own three-day silence, and a card with nothing to say reads as a card with nothing wrong. */
.sf-bcgap{flex:none;font:700 9px Inter;border-radius:999px;padding:2px 6px;background:rgba(255,255,255,.10);color:rgba(255,255,255,.60);border:1px solid rgba(255,255,255,.15);letter-spacing:.02em;white-space:nowrap}
/* SIGNAL PRIORITY, PAINTED (Craig 7/30 12:49p — 'a confusing beacon gap 3d' on a seat that had been
   shipping all day): the same fact takes two sizes, decided by whether it is the story.
   .quiet — the seat is demonstrably working (measured activity fresh), so the reporting gap is a
     DETAIL: no pill, no border, lighter weight, lowercase. Legible to a reader who looks; it no
     longer wins the glance, which is exactly the priority it had earned.
   .loud  — BOTH rails silent. Now the gap IS the story and keeps the diagnostic pill: amber, the
     drift family, never red (an absent seat is a display fact; alarm color is Craig-court only). */
.sf-bcgap.quiet{background:none;border:none;padding:2px 0 2px 2px;font:400 9px Inter;color:rgba(255,255,255,.5);letter-spacing:0}
.sf-bcgap.loud{background:rgba(217,119,6,.2);color:#f5b04c;border:1px solid rgba(217,119,6,.35)}
/* RECENT, NOT LIVE: measured activity 10-60 min old holds the card up but stops the pulse — a dot
   still breathing on an hour-quiet seat is the 7/17 'pulsed idle for hours' bug on a new rail. */
#sidebar-fleet .sf-card .sf-jb .sf-live-dot.recent{animation:none;background:rgba(63,174,82,.45)}
/* seat identity chip (HQ/PW) never clips — two letters by contract. (The 'text gives way'
   half of that law is SUPERSEDED for the name row by NAME-BEFORE-CHIP below, Craig 7/29.) */
#sidebar-fleet .sf-card .sf-nm .fs-pill{flex:none}
#sidebar-fleet .sf-card .sf-nm .sf-pill-lbl{flex:none;white-space:nowrap}
/* NAME-ROW RIGHT CLUSTER (Craig 7/18 4:26p re-rule): the model pill clusters immediately LEFT of
   the pool pill on the tile's right edge — content-sized (no empty fixed slots) so the NAME keeps
   the flex and renders untruncated ('Green Ber…'/'P…' fix). The pool pill stays the last child,
   so its right edge still lands on the same vertical line as the row-2 terminal glyph. */
/* flex:none is retired here by NAME-BEFORE-CHIP (below), which also makes this wrapper stop being a
   layout box: display:contents hands its chips straight to the .sf-nm row. See that block for why. */
#sidebar-fleet .sf-card .sf-nm .sf-nm-right{display:contents}
#sidebar-fleet .sf-card .sf-nm .sf-nm-right .ab-badge{margin-left:0}
/* ── PILL GRID LAW v2 (Craig 7/17 11:44a 'none of them are in a direct line', re-cut after
   the staircase sighting): one fixed slot grid on every tile's right side — row 1
   [badge][PLAN], row 2 [queue][TERMINAL]. The plan pill and the terminal glyph are the
   OUTER (last) slot of their row, so both right-anchor to the card's content edge and land
   on ONE clean vertical line down the rail (v1 kept plan on the inner column while the
   terminal jutted ~24px past it — the zig-zag Craig flagged). Fixed slot widths hold each
   category's x; content right-aligns so a wide pill grows leftward and the x never moves. ── */
#sidebar-fleet .sf-card .sf-slot{flex:none;display:flex;align-items:center;justify-content:flex-end}
#sidebar-fleet .sf-card .sf-slot-plan{width:50px}
#sidebar-fleet .sf-card .sf-slot-badge{width:22px}
#sidebar-fleet .sf-card .sf-slot-term{width:18px}
#sidebar-fleet .sf-card .sf-slot .fs-surf{width:18px;height:18px}
#sidebar-fleet .sf-card .sf-slot .ab-badge{margin-left:0}
/* .sf-slot-q (58px) and .sf-qpill retired 7/26: the queue depth left row 2 for the seat's FACE,
   where one .qb badge now serves every seat in every state (see the rail-wide rule below). */
/* plan pill — tight, static, never crowds the name (short 'Max #N' vocabulary by contract) */
#sidebar-fleet .sf-card .sf-plan{font:700 8.5px Inter;letter-spacing:.04em;line-height:1;padding:2.5px 5px;border-radius:5px;pointer-events:none;flex:none;white-space:nowrap}
/* THE STUDY chips (Craig 7/23 red-pen — 'I don't like how the Max #1 looks on your pill'): the
   desk's pool + model chips wear a CALM translucent pill — quiet on the warm gold ground, the same
   calm family as the fleet cards' model chips — instead of the bright planMeta fill that read as a
   highlighted/marked box clashing with the chip family. Shape here (the .sf-card rule doesn't reach
   .sf-study); no inline fill from the `calm` costume, so this owns the paint. */
#sidebar-fleet .sf-study .sf-nm-right .sf-plan{font:700 8.5px Inter;letter-spacing:.04em;line-height:1;padding:3px 7px;border-radius:999px;white-space:nowrap;flex:none;pointer-events:none;background:rgba(255,255,255,.13);color:rgba(255,255,255,.84);border:1px solid rgba(255,255,255,.16)}
/* ── UNSURE CHIP (Craig 7/30 wording law: 'a screen of stale chips cannot be read as healthy
   currency'). The '?' in the LABEL is the load-bearing signal — it survives a screenshot, a
   colorblind reader and a greyscale print, which is why the text carries it and not just the
   paint. This adds the second, non-verbal cue: the chip loses its confident fill (set neutral in
   JS so no inline style outranks it), dims, and wears a DASHED edge — the universal 'provisional'
   border. Not amber, not red: a display-truth gap is nobody's alarm (court-honest color). ── */
#sidebar-fleet .sf-card .sf-plan.unsure{border:1px dashed rgba(85,99,111,.55);opacity:.86}
#sidebar-fleet .sf-study .sf-nm-right .sf-plan.unsure{background:rgba(255,255,255,.07);color:rgba(255,255,255,.62);border:1px dashed rgba(255,255,255,.30)}
/* ═══ NAME-BEFORE-CHIP (Craig 7/29 10:11p — the HQ card rendered 'Alfred' as 'A..' because the
   desk's 'Max #5 (PW spillover)' pool chip was unshrinkable and the name was the only flexible
   thing left in the row). The NAME is the seat's IDENTITY: a card whose label reads 'A..' has
   stopped identifying its own seat. The pool chip is decoration, and the FULL pool name is already
   on the tooltip ('billing: …', planMeta). So the chip gives way first — this supersedes the
   'text gives way' half of the pill-grid law for the name row only (the row-2 slot grid stands).
   FIXED AS A CLASS, NOT AN INSTANCE: nothing below knows 'Max #5'. Any chip that can outgrow its
   label — the next long pool name, a longer model name, a narrower rail — is absorbed by the same
   rule. Mechanism is flex-shrink WEIGHTS on the CHIPS THEMSELVES, so the stages order themselves
   with no media query and no JS measurement: the pool chip's weight (99) so far exceeds the name's
   (1) that the pool chip takes essentially all the overflow down to its min-width floor, the model
   chip goes next, and only past both floors would the name begin to ellipsis (accessible, not
   unbounded — the no-truncation law's own standard). Because the floors are small, that last stage
   is unreachable at any rail the app renders: worst case both chips sit at 34px, which still leaves
   the 280px rail ~120px for a name that needs ~44.
   WHY .sf-nm-right IS display:contents. The weights only work if the chips are flex items OF THE
   NAME ROW. As a nested flex container the wrapper had to shrink first, and Chrome's automatic
   minimum size for a nested flex row resolved to its max-content (measured: cluster stuck at
   153.4px with flex-shrink:199 and min-width:auto, so the NAME ate the whole 7px deficit and
   clipped 'Builder' to 37 of 44px). Giving the wrapper a hard min-width instead only moved the lie
   — a floor below the chips' own floors clips them under overflow:hidden, which is how the pool
   chip rendered as a 10px sliver. The wrapper is a markup grouping (_nmRight), never a visual box,
   so display:contents hands the chips to .sf-nm directly and the pathology goes away. Right-anchor
   still holds: the NAME carries flex-grow:1 and pushes the chips to the content edge, exactly as
   the cluster used to (PILL GRID LAW v2's outer-column rule is unchanged). Row gap becomes .sf-nm's
   6px instead of the wrapper's 5px — the one visual consequence, Designer's to keep or re-cut.
   The POOL chip absorbs before the model chip: it is the last child by _nmRight's contract and the
   one chip whose vocabulary is open-ended. These rules must out-rank the .sf-study chip rule
   directly above (1 id + 3 classes) — hence the child combinator + full path. ═══ */
#sidebar-fleet .sf-card .sf-nm .sf-nm-right>.sf-plan,
#sidebar-fleet .sf-study .sf-study-nm .sf-nm-right>.sf-plan{flex:0 1 auto;min-width:34px;max-width:120px;overflow:hidden;text-overflow:ellipsis}
#sidebar-fleet .sf-card .sf-nm .sf-nm-right>.sf-plan:last-child,
#sidebar-fleet .sf-study .sf-study-nm .sf-nm-right>.sf-plan:last-child{flex:0 99 auto}
/* CHIP CAPS — the two numbers above are the whole legibility contract, so they are stated plainly:
   min-width 34px is the floor ('Max…' still reads as a pool, and the tooltip carries the full
   'billing: <pool>'), max-width 120px fits today's longest pool label whole ('Max #5 (PW
   spillover)' measures 113.3px). The MODEL chip caps tighter at 70px: its vocabulary is short by
   contract ('Opus 4.8' = 55.9px), and window.modelName() FALLS THROUGH TO THE RAW MODEL ID for any
   stamp its family map doesn't recognise (render.js:372) — one unmapped router stamp put a 134px
   string in this cluster and crushed the name at the full 340px rail. A cap turns that into a
   truncated chip with the full id on its own title, instead of a chip that eats the seat's name.
   Together the floor + caps are what make 'the name renders whole' TRUE rather than merely
   likely: worst case both chips sit at 34px, so the row still owes the name its ~42px. */
#sidebar-fleet .sf-card .sf-nm .sf-nm-right>.sf-mdl,
#sidebar-fleet .sf-study .sf-study-nm .sf-nm-right>.sf-plan:not(:last-child){max-width:70px}
/* Idle avatar row — 32px to match old strip idle row size */
#sidebar-fleet .sf-idles{display:flex;flex-wrap:wrap;align-items:flex-start;gap:7px;padding:5px 12px 4px}
#sidebar-fleet .sf-idles button{position:relative;background:none;border:none;padding:0;cursor:pointer;line-height:0;border-radius:10px}
/* NOTE (8/5): the width/height here are NOT the rendered size — the 7/23 "TIGHTENED ROWS" block
   near the end of this file re-declares .sf-idles img and wins the cascade. Change the size THERE.
   The same is true of .sf-card padding/margin and .sf-idles padding. */
#sidebar-fleet .sf-idles img{width:46px;height:46px;border-radius:10px;object-fit:cover;filter:saturate(.45);opacity:.7;display:block;transition:transform .12s,filter .12s,opacity .12s}
#sidebar-fleet .sf-idles button:hover img{transform:scale(1.07);filter:none;opacity:1}
/* ── THE QUEUED BADGE, ONE RULE FOR THE WHOLE RAIL (Craig 7/26 4:37p 'a Queued # next to each
   bot's face') — one legible number (1, 2, …) in the amber of the queue vocabulary, pinned to the
   top-right of whatever face it rides: an exec bench face, a fleet bench face, a shop bench face,
   a working card's 40px avatar, The Study's 44px avatar. Hidden at zero (renderer emits nothing).
   THE SHOP inherited this for free on 7/26 4:41p — the third section needed no new badge rule,
   which is the payoff of collapsing the copies below.
   This rule was previously THREE byte-identical copies scoped .sf-idles / .sf-dock / .lr-idles, so
   'same visual treatment' was a promise three separate declarations had to keep; now the rail keeps
   it structurally and a new surface inherits it by default. Every face container is already
   position:relative (.fs-av base, .sf-idles button), so one absolute rule lands on all of them.
   The Today-pane strip keeps its own copy — different container, white border, z-index. ── */
#sidebar-fleet .qb{position:absolute;top:-5px;right:-5px;background:#956010;color:#fff;font:700 10px Inter;border-radius:999px;min-width:16px;height:16px;line-height:16px;padding:0 4px;text-align:center;box-sizing:border-box;border:1.5px solid rgba(0,0,0,.35);z-index:2}
#sidebar-fleet .sf-idles .sf-idle-cap{font:400 11px Inter;color:rgba(255,255,255,.35);margin-left:4px;align-self:center}
/* ═══ THE STUDY (Craig 7/23 redesign) — Alfred leaves the fleet's basement for the TOP of the
   rail as Craig's desk: warm gold-tinted glass (distinct from the fleet's cool white-alpha
   cards), the needs-you count prominent above the fold. The butler stands at the study door. */
#sidebar-fleet .sf-study{position:relative;display:block;width:calc(100% - 16px);margin:0 8px 6px;padding:6px 12px 6px;box-sizing:border-box;text-align:left;cursor:pointer;
  background:linear-gradient(180deg,rgba(224,169,46,.14),rgba(224,169,46,.05));border:1px solid rgba(224,169,46,.30);border-radius:12px;transition:background .12s,border-color .12s,box-shadow .12s}
#sidebar-fleet button.sf-study{color:inherit;font:inherit;-webkit-appearance:none;appearance:none}
#sidebar-fleet .sf-study:hover{background:linear-gradient(180deg,rgba(224,169,46,.20),rgba(224,169,46,.08));border-color:rgba(224,169,46,.45)}
/* needs-you present → amber emphasis (calm ring, never alarm red — court-honest) */
#sidebar-fleet .sf-study.alert{border-color:rgba(224,169,46,.55);box-shadow:0 0 0 1px rgba(224,169,46,.22),0 6px 18px rgba(224,169,46,.10)}
#sidebar-fleet .sf-study-eyebrow{display:block;font:700 9.5px Inter;letter-spacing:.14em;text-transform:uppercase;color:rgba(242,203,116,.92);margin:0 0 5px}
#sidebar-fleet .sf-study-body{display:flex;align-items:center;gap:10px}
#sidebar-fleet .sf-study-txt{flex:1;min-width:0}
#sidebar-fleet .sf-study-nm{display:flex;align-items:center;gap:6px;font:600 14px Inter;color:#fff}
/* flex-shrink:0 — same law as the fleet cards' name above (NAME-BEFORE-CHIP) */
#sidebar-fleet .sf-study-nm .sf-study-nm-txt{flex:1 0 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* flex:none retired — see NAME-BEFORE-CHIP above (this cluster is the one that ate 'Alfred') */
#sidebar-fleet .sf-study-nm .sf-nm-right{display:contents}
#sidebar-fleet .sf-study-focus{display:flex;align-items:center;gap:6px;margin-top:3px;font:400 12px Inter;color:rgba(255,255,255,.66);min-width:0}
#sidebar-fleet .sf-study-focus .sf-study-focus-txt{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#sidebar-fleet .sf-study-focus .sf-live-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--brand-leaf,#3fae52);animation:fsPillDot 1.6s ease infinite}
/* desk-state stale marker (Craig 7/23 contract): a STATIC amber dot (no pulse) means the desk's
   written focus is >30min old — court-honest calm, never the live green pulse. */
#sidebar-fleet .sf-study-focus .sf-desk-dot.stale{flex:none;width:7px;height:7px;border-radius:50%;background:#d97706}
/* prominent needs-you block — one clear number; absent entirely when 0 (calm) */
#sidebar-fleet .sf-study-needs{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;min-width:46px;padding:5px 8px;border-radius:10px;background:#956010;color:#fff;line-height:1;font:800 19px Inter}
#sidebar-fleet .sf-study-needs b{font-weight:700;font-size:8.5px;letter-spacing:.05em;text-transform:uppercase;margin-top:3px;opacity:.92}
/* '+N more working' expander — the escape valve that keeps the rail scrollbar-free (Craig 7/23) */
#sidebar-fleet .sf-more-btn{display:block;width:calc(100% - 16px);margin:2px 8px 4px;padding:6px 10px;box-sizing:border-box;text-align:center;cursor:pointer;
  background:rgba(255,255,255,.06);border:1px dashed rgba(255,255,255,.22);border-radius:9px;font:600 11px Inter;color:rgba(255,255,255,.62);transition:background .12s,color .12s}
#sidebar-fleet .sf-more-btn:hover{background:rgba(255,255,255,.12);color:#fff}
/* TIGHTENED ROWS + NO SCROLLBAR (Craig 7/23): compact cards and a single-line ellipsis subtitle
   pack 7+ working lanes with zero rail scrollbar; the full subject stays on the tooltip so the
   no-truncation law (accessible, not unbounded) holds. Compact idle bench avatars, wraps only.
   ── THIS BLOCK IS THE EFFECTIVE ONE (8/5). It re-declares .sf-card, .sf-idles and .sf-idles img
   after their base rules above and therefore wins; editing the base rules alone changes nothing
   (measured 8/5 — a "46→40px" edit up there was a silent no-op because this block already said
   40px). Craig's 8/5 compression pass is applied HERE for that reason.
   The idle bench gives up its pixels before any working card does — a benched seat is by
   definition not doing anything, while a working card is the thing he opened the rail to read.
   _benchPerRow() MEASURES the rendered avatar instead of hardcoding the math, so a smaller face
   automatically fits more per row and can collapse a two-row bench into one; no companion change
   is needed there, and none should be added. */
#sidebar-fleet .sf-card{padding:5px 10px 5px 6px;margin:2px 8px}
#sidebar-fleet .sf-card .sf-jb-txt{-webkit-line-clamp:1;min-width:60px}
#sidebar-fleet .sf-idles{gap:6px;padding:4px 10px 3px}
#sidebar-fleet .sf-idles img{width:36px;height:36px;border-radius:8px}
/* ═══ SUB-AGENT LINES (Craig 7/26 — 'Alfred is running sub-agent 1: <task>') ═══════════════════
   Rendered in the app's own voice, not verbatim: the microcopy law bans state words from a tile
   task line (the DOT carries state, the TEXT carries the work) and conversational openers are
   chatter. What survives is the only part carrying new information — '↳ sub-agent 1 · <task>'.
   Scoped to the RAIL, not to .sf-card: The Study carries these too, for HQ's own sub-agents. */
#sidebar-fleet .sf-sub{display:flex;align-items:flex-start;gap:6px;margin-top:4px;padding-left:2px;font:400 11px Inter;color:rgba(255,255,255,.46);line-height:1.35;min-width:0}
#sidebar-fleet .sf-sub .sf-sub-tick{flex:none;color:rgba(255,255,255,.28);font:400 11px Inter;line-height:1.35}
#sidebar-fleet .sf-sub .sf-sub-dot{flex:none;width:5px;height:5px;border-radius:50%;background:var(--ring,#cdd8cf);margin-top:4px;opacity:.85;animation:fsSubDot 2.4s ease infinite}
#sidebar-fleet .sf-sub .sf-sub-txt{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#sidebar-fleet .sf-sub .sf-sub-txt b{font-weight:600;color:rgba(255,255,255,.62)}
#sidebar-fleet .sf-sub-more{margin-top:3px;padding-left:10px;font:600 10.5px Inter;color:rgba(255,255,255,.34);letter-spacing:.02em}
/* staggered, NOT in unison — two sub-agents breathing together reads as a strobe, not as two
   threads. The offsets ride nth-of-type so the markup carries no per-line inline style. */
#sidebar-fleet .sf-sub:nth-of-type(2) .sf-sub-dot{animation-delay:.8s}
#sidebar-fleet .sf-sub:nth-of-type(3) .sf-sub-dot{animation-delay:1.6s}
/* ═══ HQ SUB-AGENT SATELLITES (Craig 7/26 — 'HQ sub-agents as small satellites of the HQ face
   when active') ═══ Deliberately NOT portraits: an HQ sub-agent has no face in canon, and the
   style-canon law makes a face reading as a different art family a bounce — minting throwaway
   sub-agent portraits would breach it outright. These are gold pips struck in The Study's own
   gold: small pieces of Alfred, which is exactly what a sub-agent is. The pip carries the INDEX
   and the PULSE; the task text lives on the .sf-sub line and the hover title. */
#sidebar-fleet .fs-av.hq{position:relative;display:inline-block}
/* satellites orbit OUTSIDE the face — the text column yields the room, it is never overlapped.
   The margin lands only with .sat, so an empty sub_agents[] costs no space at all. */
#sidebar-fleet .fs-av.hq.sat{margin-right:13px}
#sidebar-fleet .sf-sat{position:absolute;width:16px;height:16px;border-radius:50%;background:linear-gradient(180deg,#f2cb74,#d29a25);border:1.5px solid var(--canopy,#1e3b2f);color:#3d2e08;font:800 9px/13px Inter;text-align:center;box-shadow:0 1px 3px rgba(0,0,0,.38);animation:fsSat 2.4s ease infinite}
#sidebar-fleet .sf-sat.s1{right:-8px;top:-4px}
#sidebar-fleet .sf-sat.s2{right:-12px;top:14px;animation-delay:.8s}
#sidebar-fleet .sf-sat.s3{right:-7px;top:31px;animation-delay:1.6s}
/* the overflow pip is a COUNT, not a body — hollow and still, so it never reads as a third agent */
#sidebar-fleet .sf-sat.more{background:rgba(224,169,46,.16);border-color:rgba(242,203,116,.45);color:rgba(242,203,116,.9);font:700 8.5px/13px Inter;animation:none}
@keyframes fsSat{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(.9)}}
/* ═══ THE PENNYWORTH DOCK SEAT IS RETIRED (Craig 7/26 4:41p) ═══ The dock existed because PW
   belonged to NEITHER roster (7/16 7:06p PROTECTED layout, 'I barely move my mouse above the
   dock') — a bespoke bottom-anchored tile outside both counts. THE SHOP is a section PW genuinely
   belongs to, so the exception is gone and PW renders with the ordinary seat grammar. The mouse
   travel the 7/16 layout protected is preserved by placement, not by chrome: PW is the last seat
   of the last section, still at the bottom of the rail. Its identity moved to .pwring above.
   ~40 lines of .sf-dock/.sf-dock-nm/.sf-dock-sub CSS deleted with the markup that used it — dead
   rules for a class nothing emits are how the next reader learns a surface that no longer exists. */
/* collapsed fleet — working bots keep a tile, idle bots become an avatar strip */
#ops-pane-today .lr-fleet{background:#fff;border:1px solid #e7e2d4;border-radius:12px;padding:12px 16px;margin:0 0 18px;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
#ops-pane-today .lr-fleet .lbl{display:inline-flex;align-items:center;gap:6px;font:700 12px Inter;letter-spacing:.09em;text-transform:uppercase;color:#9aa39b;white-space:nowrap;max-width:100%}
#ops-pane-today .lr-fleet .lbl>span:last-child{min-width:0;overflow:hidden;text-overflow:ellipsis}
#ops-pane-today .lr-fleet .lbl b{color:var(--brand-ok,#15803d)}
#ops-pane-today .lr-work{position:relative;display:flex;align-items:center;gap:10px;background:#f2f8f3;border:1px solid #cfe3d2;border-radius:10px;padding:8px 14px 8px 8px;flex:0 1 auto;min-width:0;cursor:pointer;text-align:left;max-width:430px}
#ops-pane-today .lr-work:hover{border-color:var(--brand-line,#b9d8b4)}
#ops-pane-today .lr-work .nm{font:600 13px Inter;color:var(--brand-deep,#1f4a2d);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* NO-TRUNCATION LAW, re-cut (Craig 7/19 5:14p): word-boundary wrap only + 2-line clamp; the full
   subject rides the tile tooltip. break-word split words per character in the busy grid's narrow
   tiles and ballooned cards to 6+ lines. */
#ops-pane-today .lr-work .jb{font:400 12.5px Inter;color:#5b6b5e;white-space:normal;overflow-wrap:normal;word-break:normal;line-height:1.35;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
/* MICROCOPY LAW (Craig 7/16): the Working pill is dead on strip tiles too — the green live
   dot on the caption line carries the state */
#ops-pane-today .lr-work .jb .sf-live-dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--brand-leaf,#3fae52);margin-right:6px;animation:fsPillDot 1.6s ease infinite}
#ops-pane-today .lr-work .jb .sf-live-dot.drift{background:#d97706}
/* drift chip, light theme (strip tiles) */
#ops-pane-today .lr-work .sf-drift{background:#fdf1e0;color:#956010;border:1px solid #f0d9b4;margin-left:6px;vertical-align:1px}
#ops-pane-today .lr-work .fs-corner{position:static;flex:none;align-self:flex-start}
/* FLEET WRAP (Craig 7/6): 4+ working = .busy — cards leave the strip row and wrap in their own
   grid (4-up desktop → 3/2/1 as the window narrows); idles + Org/Assign anchor on the last line.
   Quiet strip (≤3 working) keeps today's single row via display:contents. contain:inline-size
   stops the nowrap captions' min-content from pushing the whole main column off-viewport. */
#ops-pane-today #fleet-strip{contain:inline-size}
#ops-pane-today .lr-works{display:contents}
#ops-pane-today .lr-fleet.busy .lr-works{display:flex;flex-wrap:wrap;align-items:stretch;gap:10px 12px;flex:1 1 100%;min-width:0}
#ops-pane-today .lr-fleet.busy .lr-work{flex:1 1 calc(25% - 9px);max-width:calc(25% - 9px)}
@media (max-width:1250px){#ops-pane-today .lr-fleet.busy .lr-work{flex-basis:calc(33.33% - 8px);max-width:calc(33.33% - 8px)}}
@media (max-width:860px){#ops-pane-today .lr-fleet.busy .lr-work{flex-basis:calc(50% - 6px);max-width:calc(50% - 6px)}}
@media (max-width:600px){#ops-pane-today .lr-fleet.busy .lr-work{flex-basis:100%;max-width:100%}}
#ops-pane-today .lr-idles{display:flex;align-items:center;flex:1;min-width:180px}
#ops-pane-today .lr-idles>button{position:relative;flex:none;background:none;border:none;padding:0;margin-left:-7px;cursor:pointer;line-height:0;border-radius:50%}
#ops-pane-today .lr-idles>button:first-child{margin-left:0}
#ops-pane-today .lr-idles img{width:32px;height:32px;border-radius:50%;border:2px solid #fff;filter:saturate(.55);opacity:.8;transition:transform .12s,filter .12s,opacity .12s;display:block}
#ops-pane-today .lr-idles>button:hover{z-index:2}
#ops-pane-today .lr-idles>button:hover img{transform:scale(1.25);filter:none;opacity:1}
/* count-on-face (7/17 LAW) — same amber vocabulary as the sidebar circles */
#ops-pane-today .lr-idles .qb{position:absolute;top:-6px;right:-4px;background:#956010;color:#fff;font:700 10px Inter;border-radius:999px;min-width:16px;height:16px;line-height:16px;padding:0 4px;text-align:center;box-sizing:border-box;border:1.5px solid #fff;z-index:1}
/* nowrap + ellipsis (not bare nowrap): this caption was the last horizontal bleed at narrow widths */
#ops-pane-today .lr-idles .cnt{font:400 12.5px Inter;color:#9aa39b;margin-left:12px;white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis}
/* PW STATIONARY SEAT (7/16): the strip's static executive seat wears a real live dot when
   the platforms lane runs — the seat never relocates (PROTECTED layout, Craig 7/16 7:06p) */
.lr-idles .fs-static-dot{position:absolute;bottom:-2px;right:-2px;width:10px;height:10px;border-radius:50%;background:var(--brand-leaf,#3fae52);border:1.5px solid #fff;animation:fsPillDot 1.6s ease infinite}

/* the demoted HQ seat badge — a pointer into the Court Strip, not a second count (Craig 7/26).
   .ptr's own chrome lives in widgets/needs-you/styles.css; this only seats it in the study card. */
#sidebar-fleet .sf-study-ptr{flex:none;align-self:center;font-size:10.5px}
