/* Left-rail navigation chrome — the `rail` ui_layout.
 *
 * Markup in app/web/templates/_app_rail.html; activated by
 * `html[data-ui-layout="rail"]` (stamped by base_ds.html from
 * instance.ui_layout / AGNES_UI_LAYOUT). Loaded globally but inert
 * under the default `topnav` layout — every rule is scoped to the
 * activation attribute, so topnav instances render byte-identically.
 *
 * All colors/typography come from `--ds-*` tokens so the rail works
 * under any palette theme (paper, blue, navy, dark). Widths and
 * paddings mirror the issue-#896 prototype (240px rail, 9px item
 * radius, soft active tint).
 *
 * EVERY row shares one height (`--rail-row-h`) and one treatment, New chat
 * included — it is an ordinary `.rail-i`, not a control. See the "New chat"
 * section for the three tinted variants that were tried there and why all of
 * them are retired.
 *
 * COLOUR RULE — the accent marks WHERE YOU ARE, and nothing else:
 *   `--rail-active-bg`  → every "you are here" fill (accent tint)
 *   `--rail-hover-bg`   → hover, on every hoverable row (neutral wash)
 *   nothing standing    → no row carries a resting tint of its own
 *
 * The rail has held every version of this rule, so the reasoning matters more
 * than the values:
 *
 *   1. accent on active — broke once New chat took a tint of its own, because
 *      the accent then carried four meanings at once (the button, the active
 *      nav row, the active conversation, the active admin link) and the column
 *      stopped saying which of them you could act on;
 *   2. accent on New chat, neutral active — cost the accent its use in hover,
 *      since a hovered row and the resting button were the same pale blue;
 *   3. accent on hover, neutral active — worked, but spent colour on the one
 *      state the user needs no help identifying: the pointer is already there;
 *   4. accent on active (current), now that New chat is an ordinary row and
 *      nothing competes for it.
 *
 * (1) and (4) are the same rule; what makes it viable now is that New chat has
 * no standing tint. Active is the better owner than hover because it is
 * PERSISTENT wayfinding — you locate yourself in the column at a glance,
 * without reading — where hover is transient feedback that only has to be
 * perceptible. It also lands the accent on New chat for free on /chat, the
 * landing surface, where New chat IS the active row.
 *
 * The invariant to preserve whatever the values: exactly ONE thing in the
 * column may own the accent. Two owners is what broke every earlier version.
 */

/* Clearance for the fixed rail — conditional on the rail actually being there.
 * `data-ui-layout="rail"` is stamped on <html> by base_ds.html from instance
 * config, but _app_rail.html renders <nav class="rail"> only `{% if
 * session.user %}`. Applied unconditionally, the padding survived on pre-auth
 * pages that have no rail, so /login/password centred its card inside a box
 * phantom-shifted 240px right of the viewport (#1170). `:has()` ties the
 * reservation to the element it is reserving for; it is already the house
 * mechanism for rail-conditional layout (see chat.css / chats.css).
 *
 * `--rail-clearance` publishes that same number to everything else that has to
 * start where the page's content does. `position: fixed` chrome can't inherit
 * the padding — it is positioned against the viewport — so the bottom-docked
 * filter toolbar (`.fbar-dock`, `.fbar-dock__veil`) and the chats bulk bar
 * (`.ch-bulk`) each hardcoded `left: 240px` instead, and all three broke the
 * moment the rail could be collapsed: the toolbar sat 184px right of centre and
 * its frosted band started 184px in, clipped off the left of the content it was
 * supposed to be blurring. One variable, declared beside every `padding-left`
 * that encodes the same edge, is what keeps them from drifting again. Consumers
 * default it to `0px`, so a topnav-layout page needs no rail-specific rule at
 * all. */
html[data-ui-layout="rail"] body:has(.rail) {
    padding-left: 240px;
    --rail-clearance: 240px;
    /* The column rail has no top chrome. `--app-header-height` is still
       57px at `:root` (style-custom.css, the retired topnav's height) and
       every page-level sticky offsets off it, so without this the Library's
       bands and the admin group headers pinned 57px below a header that is
       not there. Published beside `--rail-clearance` for the same reason:
       one edge, one variable, declared where the chrome that defines it is. */
    --app-header-height: 0px;
}

html[data-ui-layout="rail"] .rail {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 240px;
    background: var(--ds-surface);
    border-right: 1px solid var(--ds-border);
    display: flex;
    flex-direction: column;
    /* Stretch children to the rail's inner width. This was originally
     * pinned to neutralize a global `nav { align-items: center }` rule in
     * style-custom.css, which cascaded onto this <nav> and collapsed every
     * item to its content width. That rule is gone (#1207 — it was also
     * silently breaking the semantic layer's category sidebar), but stretch
     * stays declared explicitly rather than relying on the flex default,
     * so the rail's layout never depends on the absence of a global rule. */
    align-items: stretch;
    gap: 2px;
    padding: 16px var(--rail-pad-x);
    z-index: 40;
    /* One row height for every rail row — nav items, conversation rows, the
       and the profile row all opt into it (min-height, so a two-line
       profile row can still grow). Scoped to the rail rather than
       :root: it is a chrome metric, not a design token. */
    --rail-row-h: 30px;

    /* ── The rail's stacking order, in one place ─────────────────────────
       It was five ad-hoc numbers (40 / 56 / 57 / 60, plus 50 inherited by the
       account menu from style-custom.css), and the gaps between them were not
       decisions — they were the order the features happened to be written in.
       Two layers is all this chrome has:

         --rail-z-chip     a label naming a glyph in the collapsed strip. It
                           annotates the column, so it sits above the page
                           chrome docked beside the rail (`.fbar-dock` and the
                           chats bulk bar, both 55) and below anything the
                           caller opened.
         --rail-z-popover  everything the caller OPENED — both menus, the
                           search results, the onboarding popover, the chat
                           flyout. They are mutually exclusive by construction
                           (opening one closes the others), so they share a
                           value rather than racing for the top. */
    --rail-z-chip: 56;
    --rail-z-popover: 60;

    /* ── The column's two left edges ─────────────────────────────────────
       A sidebar reads as designed when its rows line up, and this column had
       THREE edges: nav labels at 48px, conversation titles at 22px, "View all
       chats" at 12px. Three is what reads as accidental.

       Two is right, and it is what ChatGPT's sidebar does. Rows WITH a glyph
       put their label past it (`--rail-label-indent`, 44px). Rows WITHOUT one
       — conversation titles, the row that closes the list — start on the icon
       edge itself (`--rail-content-x`, 16px) rather than paying 28px of empty
       padding to fake an icon they do not have. In a 240px column that 28px is
       the difference between reading a conversation's subject and reading its
       first two words, and the two blocks are separated by enough whitespace
       that the eye reads them as two groups, not one ragged list.

       Derived, never repeated: change a value here and every row moves with it. */
    --rail-pad-x: 8px;
    --rail-row-pad-x: 8px;
    --rail-icon: 18px;
    --rail-icon-gap: 10px;
    --rail-label-indent: calc(var(--rail-row-pad-x) + var(--rail-icon) + var(--rail-icon-gap));
    --rail-content-x: var(--rail-row-pad-x);
    /* The "you are here" fill, shared by every active state in the rail — nav
       destinations (.rail-i.on), the active conversation (li.is-active) and the
       active Admin flyout link. Defined once so the three cannot drift apart;
       scoped to the rail rather than :root for the same reason --rail-row-h is
       (a chrome metric, not a design token).

       ACCENT — this is the one thing in the rail that owns the tint. Being on a
       page is persistent wayfinding: you should be able to locate yourself in
       the column at a glance, without reading a single label, and colour is what
       does that. A neutral wash was tried here (see the COLOUR RULE above) and
       worked, but only while the accent was busy elsewhere.

       It also gives New chat a tinted look on /chat for free, without a standing
       tint anywhere: /chat is the landing surface and New chat is the active row
       there, so the row the accent lands on most often is the one you most
       wanted coloured. */
    --rail-active-bg: var(--ds-primary-light);
    /* The hover fill, shared by every hoverable row in the rail — nav
       destinations, conversation rows and Admin flyout links. Defined once
       beside --rail-active-bg for the same reason: three rules, one value, no
       drift.

       NEUTRAL, because active owns the accent and a rail cannot spend it twice.
       This carried `--ds-primary-light` for one pass; with active tinted the
       same value, hovering any row made it read as selected. Hover only has to
       be PERCEPTIBLE — the pointer is already on the row, so the user knows what
       they are pointing at and colour tells them nothing new.

       `--ds-surface-dim` rather than a wash mixed from the ink: the ink-mix
       trick exists to stay monotonic against a NEUTRAL sibling, and hover's
       sibling is now an accent tint, so there is no ramp-ordering problem to
       solve. (For the record: the neutral surface ramp is not monotonic across
       themes — light runs surface → dim → sunken progressively darker, but in
       dark `sunken` lands darker than `dim` — so if both states ever go neutral
       again, the stronger one must be an ink-mix, not `--ds-surface-sunken`.) */
    --rail-hover-bg: var(--ds-surface-dim);
    --rail-peek-text-delay: .14s;
    --rail-peek-text-out: .1s;
    /* `visible` (not `auto`) so popovers can escape the rail's edges instead
       of being clipped — the user-menu panel below is absolutely positioned
       and depends on this. (It also served the Studio flyout, since retired.)
       The recents region scrolls internally (.rail-history-body), so the rail
       itself does not need its own scrollbar in normal use. */
    overflow: visible;
    font-family: var(--ds-font);
}

/* 14px below the wordmark — between the 8px this carried while the rail had no
   section labels and the 18px of its first version. The 8px was tuned for a
   logo sitting directly above New chat, where a deep trough read as two
   unrelated zones when they are one surface (the logo's href is `home_route`,
   which defaults to /dashboard → /chat, exactly where `#new-chat` goes). With
   Pinned/Chats labels below, the top of the rail carries more structure and the
   wordmark needs to stop reading as part of the first row. Still asymmetric on
   purpose: the mark keeps its 6px above.
   The ≤1024px bar overrides it back to 8px — there the logo shares a row with
   the collapse toggle, and extra bottom padding sits it visually high.

   This padding/gap now lives on `.rail-logo-row`, the wrapper that also
   carries `#rail-toggle` (the one collapse/expand control, ChatGPT's
   pattern) — moved here so the row's own geometry doesn't depend on which
   of its two children happens to be visible. */
html[data-ui-layout="rail"] .rail-logo-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    /* Named because `#rail-toggle` is a STATIC flex child here and an
       ABSOLUTE one in the collapsed strip, and the two have to centre on the
       same band or the control moves the instant the class flips. A flex
       child centres in the CONTENT box; an absolute child inset `top: 0;
       bottom: 0` centres in the PADDING box, and this row's padding is
       asymmetric (6 top, 14 bottom) — so the toggle rose 4px on every click
       while the row around it stood still, which reads as a rendering
       glitch rather than a state change (#1956 item 4). Both rules take the
       band from these two, so neither can drift from the other again. */
    --rail-logo-row-pt: 6px;
    --rail-logo-row-pb: 10px;
    padding: var(--rail-logo-row-pt) 8px var(--rail-logo-row-pb);
    flex-shrink: 0;
}

html[data-ui-layout="rail"] .rail-logo {
    display: flex;
    align-items: center;
    gap: 9px;
    min-width: 0;
    flex: 1 1 auto;
    overflow: hidden;
    color: var(--ds-text-primary);
    text-decoration: none;
    /* Containing block for `.rail-logo-mark` — the collapsed-strip stand-in for
       a custom lockup, which is positioned rather than laid out so the swap
       between the two costs no layout. See that block below. */
    position: relative;
}

/* `img` as well as `svg`: the operator's brand knobs take pasted HTML, and an
   `<img src="data:image/svg+xml;base64,…">` is as common a paste as an inline
   `<svg>`. Sized only as `svg`, an `<img width="76">` rendered at its own size
   and `.rail-logo`'s clip left a corner of it — a shard, not a logo. */
html[data-ui-layout="rail"] .rail-logo svg,
html[data-ui-layout="rail"] .rail-logo img {
    height: 28px;
    width: auto;
    flex-shrink: 0;
}

/* ── A custom lockup and its collapsed MARK ──────────────────────────────
   Only rendered for an instance with `instance.logo_svg` set; the built-in
   brand is an orb plus a text span and needs none of this.

   The problem this solves (#1898 item 5): a lockup is one <svg>, the collapsed
   strip gives it a ~40px box, and `.rail-logo`'s `overflow: hidden` therefore
   cropped a wordmark mid-glyph — half a word, which reads as a broken image
   rather than as a narrow sidebar. Every other label in the rail survives the
   collapse because it is a SEPARATE element the strip can hide outright; the
   lockup had no such second half, so it got one (`_app_rail.html`).

   `left: 4.5px` is the same formula the collapsed `.rail-logo` padding uses:
   the strip's centre is 27.5px, the rail's own padding puts the row's content
   edge at 8px, so a 30px mark starts 4.5px in. It is a constant rather than
   inherited padding because an absolutely positioned box is laid out against
   the PADDING box — it would ignore that padding, and animating it would move
   the mark for no reason while it is fading out anyway. */
html[data-ui-layout="rail"] .rail-logo-full {
    display: flex;
    align-items: center;
    min-width: 0;
    overflow: hidden;
}

html[data-ui-layout="rail"] .rail-logo-mark {
    position: absolute;
    left: 4.5px;
    top: 50%;
    width: 30px;
    height: 30px;
    display: grid;
    place-items: center;
    transform: translateY(-50%);
    /* Hidden by default: expanded is the lockup's state, and the mark only
       exists for the strip. `visibility` as well as `opacity`, so it never takes
       the click that belongs to the lockup underneath it. */
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

html[data-ui-layout="rail"] .rail-logo-mark svg,
html[data-ui-layout="rail"] .rail-logo-mark img {
    height: 26px;
    width: 26px;
    flex-shrink: 0;
    /* A square box; a raster or non-square mark is letterboxed, not stretched. */
    object-fit: contain;
}

/* The DERIVED mark — the brand's initial, for an operator who set a lockup but
   no monogram. Deliberately quiet: a neutral tile in the page's own dim
   surface, not an invented brand colour we have no way to know. An operator who
   wants their own mark sets `instance.logo_mark_svg` and this never renders. */
html[data-ui-layout="rail"] .rail-logo-mono {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: var(--ds-radius-btn);
    background: var(--ds-surface-dim);
    color: var(--ds-text-primary);
    /* 15px/700 in a 28px tile — the wordmark it stands in for is 16px/650, and
       a single glyph needs the extra weight to carry the same presence. */
    font-size: 15px;
    font-weight: 700;
    letter-spacing: 0;
    line-height: 1;
}

/* The ONE rail-width control (collapse when expanded, expand when
   collapsed). Persistently visible here, in the expanded row — the
   collapsed-strip overrides below (icon mode) are where it goes quiet
   until hover/focus/touch reveals it in the logo's place instead. */
html[data-ui-layout="rail"] .rail-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    min-width: 24px;
    min-height: 24px;
    flex-shrink: 0;
    border: none;
    border-radius: var(--ds-radius-pill, 999px);
    background: none;
    color: var(--ds-text-secondary);
    cursor: pointer;
}

html[data-ui-layout="rail"] .rail-toggle:hover {
    background: var(--rail-hover-bg);
    color: var(--ds-text-primary);
}

html[data-ui-layout="rail"] .rail-toggle:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: var(--ds-focus-outline-offset);
}

/* 17px, a touch larger than the 15px a chevron needed: this glyph's meaning
   lives in its interior (a frame plus a divider) rather than in one stroke's
   direction, and it turns to mush below ~16px. */
html[data-ui-layout="rail"] .rail-toggle svg {
    width: 17px;
    height: 17px;
}

html[data-ui-layout="rail"] .rail-search-icon,
html[data-ui-layout="rail"] .rail-icon-tip {
    display: none;
}

/* Collapse toggle for the ≤1024px row layout (see the media query below) —
   inert and hidden above it, since the column layout has room for
   everything and nothing to collapse. */
html[data-ui-layout="rail"] .rail-collapse-toggle {
    display: none;
}

/* Both nav zones + recents + Admin are wrapped in this div so the ≤1024px
   layout has a single thing to collapse. Above that breakpoint it must be a
   completely transparent wrapper — matching .rail's own flex/gap exactly — or
   nesting it introduces new spacing between items that used to be direct flex
   children of .rail (a change to the byte-identical default column layout,
   which CLAUDE.md's design-system contract forbids).
   flex:1 1 auto + min-height:0 carries forward what .rail-history relied on
   .rail itself for: growing to fill the remaining fixed-height column so
   its own flex:1/overflow:hidden can turn IT into the scrolling region,
   instead of the whole rail growing past the viewport. No `overflow`
   here — the Studio hover flyout (rail-nav, position:absolute) still needs
   to escape rightward through this wrapper exactly as it did through .rail
   directly. */
html[data-ui-layout="rail"] .rail-collapsible {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1 1 auto;
    min-height: 0;
}

/* Agnes brand orb — the raster brand mark sitting before the wordmark. */
html[data-ui-layout="rail"] .rail-orb {
    width: 30px;
    height: 30px;
    flex-shrink: 0;
}

/* Agnes wordmark beside the orb. */
/* Two lines, mirroring the account block at the foot of the column. The two
   bracket the sidebar and should read as a pair rather than as a wordmark at
   one end and a card at the other. `flex: 1` so a long instance name truncates
   against the toggle instead of pushing it off the row. */
html[data-ui-layout="rail"] .rail-logo-txt {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1px;
    min-width: 0;
    flex: 1 1 auto;
    overflow: hidden;
}

html[data-ui-layout="rail"] .rail-logo-name {
    font-size: 15px;
    /* 600, not a half-step: 650 only exists on a VARIABLE font, and where Inter
       is loaded as static faces it snaps to 700 — chrome that renders a full
       weight heavier depending on how the font shipped. */
    font-weight: 600;
    letter-spacing: -.012em;
    color: var(--ds-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The instance's full name, rendered only when it differs from the mark above
   it (see the template). Same relationship to its name as the account block's
   email has to its own: one step down, muted. */
html[data-ui-layout="rail"] .rail-logo-sub {
    font-size: 11.5px;
    font-weight: 400;
    color: var(--ds-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

html[data-ui-layout="rail"] .rail-nav {
    display: flex;
    flex-direction: column;
    /* A custom property, not a literal, because the folded Chats row has to
       cancel this exact value — `gap` is spent on a zero-height flex item just
       the same, so without that the collapsed-only row leaves 3px of nothing
       between New chat and the conversation list. */
    --rail-nav-gap: 1px;
    gap: var(--rail-nav-gap);
    flex-shrink: 0;
}

/* TYPE SCALE NOTE (the whole column, not just this rule): navigation type sits
   one step BELOW page type — 13px rows, 16px wordmark, 16px icons — so the
   content leads and the chrome recedes. It was 14px rows under a 19px wordmark,
   which read as a second page competing with the one you opened. The active row
   earns WEIGHT as well as tint (600 vs 500), so "where am I" survives a
   screenshot in greyscale and does not lean entirely on colour. */
html[data-ui-layout="rail"] .rail-i {
    display: flex;
    align-items: center;
    /* Part of the column's grid — see `--rail-label-indent`. Changing it here
       moves every icon-less row's indent with it, by construction. */
    gap: var(--rail-icon-gap);
    /* min-height, not height: the row must still grow for a wrapped label
       rather than clipping it. Vertical padding stays below the row height so
       short rows are height-driven and every rail row lines up. */
    min-height: var(--rail-row-h);
    padding: 4px var(--rail-row-pad-x);
    border-radius: 8px;
    cursor: pointer;
    color: var(--ds-text-secondary);
    font-family: inherit;
    font-weight: 500;
    font-size: 13px;
    /* Explicit line-height so the label's line-box never exceeds the icon and
       every row resolves to the same height from `--rail-row-h` rather than from
       whatever its text happens to measure. */
    line-height: 1.2;
    text-decoration: none;
    border: none;
    background: none;
    width: 100%;
    text-align: left;
}

html[data-ui-layout="rail"] .rail-i svg {
    /* The grid's value, not this rule's — every label's indent is derived
       from it, so the two can never disagree. */
    width: var(--rail-icon);
    height: var(--rail-icon);
    flex-shrink: 0;
}

/* Hover — NEUTRAL, see `--rail-hover-bg`. The accent belongs to the active row;
   hover only has to be perceptible. */
html[data-ui-layout="rail"] .rail-i:hover {
    background: var(--rail-hover-bg);
    color: var(--ds-text-primary);
}

/* Active destination — the ACCENT tint, see `--rail-active-bg`. Ink goes with
   the fill so the row never reads as blue-behind-grey-text. Hovering the active
   row replaces the tint with the neutral wash, which is a little odd on paper
   but right in practice: the two states are mutually exclusive, and hover
   confirms the pointer is over a row you can click even when you are already on
   it. */
html[data-ui-layout="rail"] .rail-i.on {
    background: var(--rail-active-bg);
    color: var(--ds-primary-dark);
    font-weight: 600;
}

html[data-ui-layout="rail"] .rail-i:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: var(--ds-focus-outline-offset);
}

html[data-ui-layout="rail"] .rail-i-count {
    flex: 0 0 auto;
    min-width: 18px;
    padding: 1px 6px;
    border-radius: var(--ds-radius-pill);
    background: var(--ds-surface-sunken);
    color: var(--ds-text-muted);
    font-size: 11px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1.5;
    text-align: center;
}

html[data-ui-layout="rail"] .rail-setup .rail-i-count,
html[data-ui-layout="rail"] .rail-getstarted-btn .rail-i-count {
    /* Slightly stronger ink than the neutral default: here the number is the
       row's payload rather than a decoration on someone else's row. */
    color: var(--ds-text);
}

/* On the active row the count has to stay legible against the accent tint. */
html[data-ui-layout="rail"] .rail-i.on .rail-i-count {
    background: var(--ds-surface);
    color: var(--ds-primary-dark);
}

/* A row that reports a STATE rather than offering a destination of the same
   rank — today only "Admin paused" (elevation is off for this browser, so the
   Admin row is correctly gone and this says why). Quieter than a live
   destination and never carries the active tint: it must not compete with the
   rows that actually take you somewhere in the app. It is still a link — to
   the switch on /me/profile — so hover behaves normally. */
html[data-ui-layout="rail"] .rail-i--muted {
    color: var(--ds-text-muted);
}
html[data-ui-layout="rail"] .rail-i--muted .rail-i-label {
    font-style: italic;
}


/* The label takes the free space so any trailing element in a nav item hugs
   the right edge, and truncates rather than wrapping — same treatment the
   conversation titles get, so a long label can never re-flow the ladder. */
html[data-ui-layout="rail"] .rail-i-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

html[data-ui-layout="rail"] .rail-i-kbd {
    flex: 0 0 auto;
    padding: 0 5px;
    border: 1px solid var(--ds-border);
    border-radius: 5px;
    color: var(--ds-text-muted);
    font-family: inherit;
    font-size: 10.5px;
    font-weight: 500;
    line-height: 1.5;
}

html[data-ui-layout="rail"] .rail-i-kbd--hover {
    opacity: 0;
    transition: opacity var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease);
}
html[data-ui-layout="rail"] .rail-i:is(:hover, :focus-visible) > .rail-i-kbd--hover {
    opacity: 1;
}

html[data-ui-layout="rail"] .rail-toggle-tip {
    display: none;
}

html[data-ui-layout="rail"] .agnes-search {
    width: min(640px, calc(100vw - 32px));
    max-height: min(560px, 76vh);
    margin: 12vh auto auto;
    padding: 0;
    border: 1px solid var(--ds-border);
    border-radius: 14px;
    background: var(--ds-surface);
    color: var(--ds-text-primary);
    box-shadow: var(--ds-shadow-lg);
    overflow: hidden;
}
html[data-ui-layout="rail"] .agnes-search[open] {
    display: flex;
    flex-direction: column;
}
html[data-ui-layout="rail"] .agnes-search::backdrop {
    background: color-mix(in srgb, black 32%, transparent);
}
html[data-ui-layout="rail"] .rail-shortcuts {
    width: min(420px, calc(100vw - 32px));
    margin: 14vh auto auto;
    padding: 0;
    border: 1px solid var(--ds-border);
    border-radius: 14px;
    background: var(--ds-surface);
    color: var(--ds-text-primary);
    box-shadow: var(--ds-shadow-lg);
}
html[data-ui-layout="rail"] .rail-shortcuts::backdrop {
    background: color-mix(in srgb, black 32%, transparent);
}
html[data-ui-layout="rail"] .rail-shortcuts-body {
    padding: 14px 16px 16px;
}
html[data-ui-layout="rail"] .rail-shortcuts-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 8px;
}
html[data-ui-layout="rail"] .rail-shortcuts-title {
    margin: 0;
    font-size: 15px;
    font-weight: 600;
    color: var(--ds-text-primary);
}
html[data-ui-layout="rail"] .rail-shortcuts-close {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 0;
    border-radius: 7px;
    background: none;
    color: var(--ds-text-secondary);
    cursor: pointer;
}
html[data-ui-layout="rail"] .rail-shortcuts-close svg {
    width: 16px;
    height: 16px;
}
html[data-ui-layout="rail"] .rail-shortcuts-close:hover {
    background: var(--rail-hover-bg, var(--ds-surface-sunken));
    color: var(--ds-text-primary);
}
html[data-ui-layout="rail"] .rail-shortcuts-close:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: var(--ds-focus-outline-offset);
}
html[data-ui-layout="rail"] .rail-shortcuts-list {
    margin: 0;
}
html[data-ui-layout="rail"] .rail-shortcuts-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 8px 0;
    border-top: 1px solid var(--ds-border);
}
html[data-ui-layout="rail"] .rail-shortcuts-row:first-child {
    border-top: 0;
}
html[data-ui-layout="rail"] .rail-shortcuts-row dt {
    font-size: 13px;
    color: var(--ds-text-primary);
}
html[data-ui-layout="rail"] .rail-shortcuts-row dd {
    margin: 0;
}
html[data-ui-layout="rail"] .rail-shortcuts-row kbd {
    padding: 1px 7px;
    border: 1px solid var(--ds-border);
    border-radius: 5px;
    background: var(--ds-surface-sunken);
    color: var(--ds-text-secondary);
    font-family: inherit;
    font-size: 12px;
    line-height: 1.6;
    white-space: nowrap;
}
@media (hover: none) and (pointer: coarse) {
    html[data-ui-layout="rail"] .rail-help-item.rail-help-shortcuts {
        display: none;
    }
}
html[data-ui-layout="rail"] .agnes-search-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px;
    border-bottom: 1px solid var(--ds-border);
}
html[data-ui-layout="rail"] .agnes-search-icon {
    width: 18px;
    height: 18px;
    flex: none;
    color: var(--ds-text-muted);
}
html[data-ui-layout="rail"] .agnes-search-input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0;
    border: 0;
    outline: 0;
    background: none;
    font-family: inherit;
    font-size: 15px;
    color: var(--ds-text-primary);
}
html[data-ui-layout="rail"] .agnes-search-input::placeholder { color: var(--ds-text-muted); }
html[data-ui-layout="rail"] .agnes-search-esc,
html[data-ui-layout="rail"] .agnes-search-foot kbd {
    padding: 0 6px;
    border: 1px solid var(--ds-border);
    border-radius: 5px;
    background: var(--ds-surface-sunken);
    color: var(--ds-text-secondary);
    font-family: inherit;
    font-size: 11px;
    line-height: 1.6;
}
html[data-ui-layout="rail"] .agnes-search-results {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 6px;
}
html[data-ui-layout="rail"] .agnes-search-typed:not(:last-child) {
    margin-bottom: 4px;
    padding-bottom: 4px;
    border-bottom: 1px solid var(--ds-border);
}
html[data-ui-layout="rail"] .agnes-search-typed .agnes-search-row-main {
    flex-direction: row;
    align-items: baseline;
    gap: 8px;
}
html[data-ui-layout="rail"] .agnes-search-typed .agnes-search-row-sub {
    flex: none;
}
html[data-ui-layout="rail"] .agnes-search-group {
    padding: 10px 10px 4px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--ds-text-muted);
}
html[data-ui-layout="rail"] .agnes-search-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: 8px;
    color: var(--ds-text-primary);
    font-size: 13.5px;
    text-decoration: none;
    cursor: pointer;
}
html[data-ui-layout="rail"] .agnes-search-row[aria-selected="true"] {
    background: var(--ds-surface-sunken);
}
html[data-ui-layout="rail"] .agnes-search-row[aria-selected="true"] .agnes-search-row-where,
html[data-ui-layout="rail"] .agnes-search-row[aria-selected="true"] .agnes-search-row-sub {
    color: var(--ds-text-secondary);
}
@media (forced-colors: active) {
    html[data-ui-layout="rail"] .agnes-search-row[aria-selected="true"] {
        outline: 2px solid Highlight;
        outline-offset: -2px;
    }
}
html[data-ui-layout="rail"] .agnes-search-status {
    padding: 4px 16px 10px;
    font-size: 13px;
    color: var(--ds-text-muted);
}
html[data-ui-layout="rail"] .agnes-search-status[hidden] {
    display: none;
}
html[data-ui-layout="rail"] .agnes-search-row-ico {
    width: 16px;
    height: 16px;
    flex: none;
    color: var(--ds-text-muted);
}
html[data-ui-layout="rail"] .agnes-search-row--ask .agnes-search-row-ico {
    color: var(--ds-primary);
}
html[data-ui-layout="rail"] .agnes-search-row-main {
    display: flex;
    flex-direction: column;
    gap: 1px;
    flex: 1 1 auto;
    min-width: 0;
}
html[data-ui-layout="rail"] .agnes-search-row-title,
html[data-ui-layout="rail"] .agnes-search-row-sub {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
html[data-ui-layout="rail"] .agnes-search-row-sub {
    font-size: 12px;
    color: var(--ds-text-secondary);
}
html[data-ui-layout="rail"] .agnes-search-row-where {
    flex: none;
    font-size: 11.5px;
    color: var(--ds-text-muted);
}
html[data-ui-layout="rail"] .agnes-search-foot {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 16px;
    padding: 8px 14px;
    border-top: 1px solid var(--ds-border);
    font-size: 12px;
    color: var(--ds-text-muted);
}
html[data-ui-layout="rail"] .agnes-search-foot kbd {
    margin-right: 2px;
}
@media (max-width: 600px) {
    html[data-ui-layout="rail"] .agnes-search {
        width: calc(100vw - 16px);
        max-height: calc(100vh - 16px);
        margin: 8px auto auto;
    }
}

/* ── New chat ──────────────────────────────────────────────────────────
   NO rules of its own. It is an ordinary `.rail-i` row — the block above styles
   it exactly like Library / Agents / Admin — and it highlights only via `.on`.

   It spent a while as a `.rail-compose` control: a pale-blue borderless block,
   38px instead of the 34px ladder, because as a plain row it genuinely is hard
   to tell from the conversations directly beneath it (same `--rail-row-h`, same
   `--ds-text-secondary`, 14px/500 against 13.5px/500, 9px radius against 8px).
   Three variants of that treatment were tried and all are retired: grey fill +
   `--ds-border` outline + tinted icon chip (three devices for one job, and it
   read as a disabled control), solid `--ds-primary` (far too loud for a 240px
   column), and the pale `--ds-primary-light` fill.

   Why none of them came back: a standing tint on ONE row consumed the rail's
   entire accent budget. Hover could not use the accent, because a hovered
   Library row became pixel-identical to New chat at rest; and `.on` needed an
   inset ring purely to tell two pale-blue things apart. Interaction feedback
   that fires on every row, every time the pointer moves, is worth more than
   decoration on a single row — so the accent went to hover
   (`--rail-hover-bg`) and this row went neutral.

   What distinguishes it without colour: the `+` glyph (no conversation row has
   an icon), its position at the top of the column, and the 8px gap below. If a
   future pass wants it louder, spend something OTHER than the accent — weight
   and ink are unused here (every resting row is `--ds-text-secondary`/500, so
   `--ds-text-primary`/600 is available and costs no colour). */

/* Separates New chat from the conversations below it. The rail's own 2px gap
   read as "next row in the same list"; 8px is all the separation the two need —
   and it is now the only thing between the verb and the first section label, so
   the label needs no top margin of its own (.rail-chatsec:first-child below). */
/* The conversation list belongs TO New chat — start one, or return to one you
   already had — so it sits directly under it. This was 8px, set when a "PINNED"
   label headed the list and needed air above it to read as a heading; with the
   labels gone that air just detached the list from the row it belongs to and
   left it floating in the middle of the column. 3px is the same step the nav
   rows use between themselves, which is what makes the list read as continuing
   the ladder rather than starting a new block. */
html[data-ui-layout="rail"] .rail-nav-top {
    margin-bottom: calc(var(--rail-nav-gap, 1px) - 2px);
}

/* ── Conversations ─────────────────────────────────────────────────────
   ONE unlabelled list — pinned rows, then the feed, then the row that closes it
   — rendered by two <section>s inside ONE scroll box (two render targets, no
   labels, no gap; see "The two render targets" below). The region occupies the
   slack between the two fixed zones, so the bottom zone stays pinned to the
   foot however long the list gets. The <ul id="chat-list">
   reuses the chat page's ids/classes so chat.js renders straight into it on
   /chat; rail_history.js fills it (and <ul id="pinned-chat-list">) on every
   other page. Nothing truncates either list — see .rail-history-body. */
html[data-ui-layout="rail"] .rail-history {
    min-height: 0;
    display: flex;
    flex-direction: column;
    /* Take the remaining vertical space so the list — not the whole rail — is
       what scrolls. min-height:0 lets it shrink below content height, and
       overflow:hidden forces the flex child (.rail-history-body) to respect
       that bound and scroll internally instead of spilling into the zone
       below. */
    flex: 1 1 auto;
    overflow: hidden;
}

/* Scroll region — ALWAYS scrolling, never truncated.
   `flex: 1 1 0` makes it grow strictly into the space .rail-history leaves (a
   `flex-basis: auto` would size to content and refuse to shrink, spilling older
   rows past the clip instead of scrolling them), and `min-height: 0` lets it
   shrink below content height so a short viewport shrinks the list rather than
   pushing the bottom zone off screen — the rail itself is `overflow: visible`
   and cannot scroll, so anything forced past the fold would be unreachable.
   That is also why there is deliberately no `min-height` floor here.

   This used to have TWO states: `flex: 0 1 auto; overflow: hidden` while
   truncated to five rows, and these values once expanded. The truncation and
   its "View all chats" / "Show less" toggle are gone — this region is already
   exactly the rail's free space, so the cap showed five rows on a screen with
   room for nine and the toggle existed only to undo a limit we imposed
   ourselves. See rail_history.js for what went with it. */
html[data-ui-layout="rail"] .rail-history-body {
    display: flex;
    flex-direction: column;
    min-height: 0;
    flex: 1 1 0;
    overflow-y: auto;
    margin-top: 2px;
    /* Room for the scrollbar so rows don't sit flush against the rail edge. */
    padding-right: 2px;

    /* ── Scroll fade ───────────────────────────────────────────────────
       This region is exactly the rail's free space, so on most viewports there
       ARE rows past one edge or the other. With a hard clip the first/last
       visible title is sliced mid-glyph and reads as a rendering fault, not as
       "keep scrolling" — the one thing the cut needs to say.

       A gradient mask says it instead: rows dissolve into the rail at whichever
       edge still has content behind it. Masking (not a gradient overlay) is
       deliberate — the fade has no colour of its own, so it needs no value that
       has to be kept in sync with `--ds-surface` across four palette themes and
       light/dark, and it cannot go wrong when a hovered/active row paints its
       own tint underneath.

       Both edges are off by default and switched on per-edge by
       rail_history.js (.is-fade-top / .is-fade-bottom) from the scroll
       position, so a list that fits entirely — the common case early on, with
       two or three conversations — is not dimmed at either end for no reason.
       At 0px the two stops collapse onto the same position and the mask is a
       no-op, so the resting state costs nothing but a stacking context. */
    --rail-fade: 20px;
    --rail-fade-top: 0px;
    --rail-fade-bottom: 0px;
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, black var(--rail-fade-top), black calc(100% - var(--rail-fade-bottom)), transparent 100%);
            mask-image: linear-gradient(to bottom, transparent 0, black var(--rail-fade-top), black calc(100% - var(--rail-fade-bottom)), transparent 100%);
}

/* ── The flyout surface ────────────────────────────────────────────────────
   Nothing at all in the expanded rail: a transparent flex column that passes
   `.rail-history`'s height straight through to the scroll body, which is
   exactly the box that body sat in before this element existed. Every rule
   that matters here is in the collapse section — this is where the flyout
   comes from, not where it lives.

   Why the wrapper exists rather than flying `.rail-history` itself out: that
   section is what RESERVES the conversation region's height in the collapsed
   column. It is hidden by `visibility`, never by `display`, precisely so the
   bottom zone does not move (see the big note in the collapse section). Taking
   it out of flow to position it would have slid Library, Agents, Admin, the
   `?` and the account up the instant the flyout opened. The section stays put
   and its contents leave instead. */
html[data-ui-layout="rail"] .rail-history-panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    flex: 1 1 auto;
}

html[data-ui-layout="rail"] .rail-history-foot {
    display: none;
    align-items: center;
    justify-content: space-between;
    gap: 9px;
    min-height: var(--rail-row-h);
    margin-top: 4px;
    padding: 6px 11px;
    border-top: 1px solid var(--ds-border);
    border-radius: 0 0 9px 9px;
    color: var(--ds-primary-dark);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.2;
    text-decoration: none;
}
html[data-ui-layout="rail"] .rail-history-foot svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}
html[data-ui-layout="rail"] .rail-history-foot:hover {
    background: var(--rail-hover-bg);
}
html[data-ui-layout="rail"] .rail-history-foot:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: var(--ds-focus-outline-offset);
}

html[data-ui-layout="rail"] .rail-history-body.is-fade-top:not(:has(.rail-chatsec-hd)) {
    --rail-fade-top: var(--rail-fade);
}

html[data-ui-layout="rail"] .rail-history-body.is-fade-bottom {
    --rail-fade-bottom: var(--rail-fade);
}

/* Forced-colors users have asked the OS for maximum contrast; a decorative
   fade to transparent is the opposite of that. Drop it — the scrollbar carries
   the same "there is more" signal. */
@media (forced-colors: active) {
    html[data-ui-layout="rail"] .rail-history-body {
        -webkit-mask-image: none;
                mask-image: none;
    }
}

/* The Chats row is the collapsed form of the conversation zone, so its DEFAULT
   is folded: at every width where the lists are on screen, the stand-in is not.
   Only the 56px icon strip unfolds it (see `.rail-i--collapsed-only` inside the
   `min-width: 1025px` block at the foot of this file, which is also where the
   full rationale and the timing live).

   Deliberately outside that media query: below 1025px the rail is a bar with no
   icon mode, so a default scoped to the strip would leave this row unstyled and
   visible above the very lists it substitutes for. */
html[data-ui-layout="rail"] .rail .rail-i--collapsed-only {
    height: 0;
    min-height: 0;
    /* Zero height is not zero SPACE: the parent's `gap` is charged for this item
       whatever its height, so a folded row still pushed the conversation list
       3px down. Give that back, so folded means genuinely absent. */
    margin: calc(-1 * var(--rail-nav-gap, 3px)) 0 0;
    padding-top: 0;
    padding-bottom: 0;
    opacity: 0;
    visibility: hidden;
    overflow: hidden;
    transition: height var(--ds-motion-fast) var(--ds-ease-standard) var(--rail-peek-text-delay),
                padding var(--ds-motion-fast) var(--ds-ease-standard) var(--rail-peek-text-delay),
                opacity var(--ds-motion-fast) var(--ds-ease-standard) var(--rail-peek-text-delay),
                visibility 0s linear calc(var(--rail-peek-text-delay) + var(--ds-motion-fast));
}

html[data-ui-layout="rail"] .rail-chatsec {
    display: block;
}

html[data-ui-layout="rail"] .rail-chatsec + .rail-chatsec {
    margin-top: 0;
}

html[data-ui-layout="rail"] .rail-chatsec-title {
    margin: 0;
    font: inherit;
    letter-spacing: inherit;
    color: inherit;
}

html[data-ui-layout="rail"] .rail-chatsec-toggle {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    min-height: 24px;
    margin-left: -6px;
    padding: 0 6px;
    border: 0;
    border-radius: 6px;
    background: none;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    color: inherit;
    cursor: pointer;
}

html[data-ui-layout="rail"] .rail-chatsec-toggle:hover {
    color: var(--ds-text-secondary);
}

html[data-ui-layout="rail"] .rail-chatsec-toggle:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: var(--ds-focus-outline-offset);
}

html[data-ui-layout="rail"] .rail-chatsec-chev {
    width: 12px;
    height: 12px;
    flex: 0 0 auto;
    transition: transform var(--ds-motion-fast) var(--ds-ease-standard);
}

html[data-ui-layout="rail"] .rail-chatsec.is-collapsed .rail-chatsec-chev {
    transform: rotate(-90deg);
}

html[data-ui-layout="rail"] .rail-chatsec.is-collapsed .rail-chatsec-body {
    display: none;
}

html[data-ui-layout="rail"] .rail-chatsec.is-collapsed .rail-chatsec-toggle[data-attention]::after {
    content: "";
    width: 6px;
    height: 6px;
    margin-left: 2px;
    border-radius: 50%;
    background: var(--ds-accent-warn-ink);
}

@media (hover: hover) and (pointer: fine) {
    html[data-ui-layout="rail"] .rail-history-all,
    html[data-ui-layout="rail"] .rail-chatsec-chev {
        transition: opacity var(--ds-motion-fast) var(--ds-ease-standard), transform var(--ds-motion-fast) var(--ds-ease-standard);
    }
    html[data-ui-layout="rail"] .rail-chatsec:not(.is-collapsed) .rail-chatsec-chev,
    html[data-ui-layout="rail"] .rail-history-all:not([aria-current]) {
        opacity: 0;
    }
    html[data-ui-layout="rail"] .rail-chatsec:hover .rail-chatsec-chev,
    html[data-ui-layout="rail"] .rail-chatsec-hd:has(:focus-visible) .rail-chatsec-chev,
    html[data-ui-layout="rail"] .rail-chatsec:hover .rail-history-all,
    html[data-ui-layout="rail"] .rail-chatsec-hd:has(:focus-visible) .rail-history-all {
        opacity: 1;
    }
}

html[data-ui-layout="rail"] .rail-chatsec-hd {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--ds-surface);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin: 20px 0 2px;
    padding: 0 var(--rail-row-pad-x) 0 var(--rail-content-x);
    font-family: inherit;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .04em;
    line-height: 1.6;
    text-transform: uppercase;
    color: var(--ds-text-muted);
}

/* The list itself doesn't scroll — its parent (.rail-history-body) does — so
   neutralize the base chat.css `.cloud-chat-list { overflow-y:auto; flex:1 }`
   that would otherwise create a nested scroll container on /chat. */
html[data-ui-layout="rail"] .rail-history .cloud-chat-list {
    list-style: none;
    margin: 0;
    padding: 0;
    overflow: visible;
    flex: 0 0 auto;
}

html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id] {
    display: flex;
    min-height: var(--rail-row-h);
    padding: 0;
    border-radius: 8px;
    font-size: 13px;
    line-height: 1.3;
    color: var(--ds-text-secondary);
    border: 1px solid transparent;
}

html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id].is-over-limit {
    display: none;
}

html[data-ui-layout="rail"] .rail-history .cloud-chat-list-link {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px var(--rail-row-pad-x) 4px calc(var(--rail-content-x) - 1px);
    border-radius: inherit;
    color: inherit;
    text-decoration: none;
}

html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id] + li[data-id] {
    margin-top: 1px;
}

html[data-ui-layout="rail"] .rail-history #pinned-chat-list:has(li[data-id]) + #chat-list:has(li[data-id])::before {
    content: "";
    display: block;
    height: 1px;
    margin: 6px var(--rail-row-pad-x) 6px calc(var(--rail-content-x) - 1px);
    background: var(--ds-border);
}

html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id]:hover {
    background: var(--rail-hover-bg);
    color: var(--ds-text-primary);
}

/* The conversation you are reading — the accent `--rail-active-bg`, like every
   other active state in the rail.

   The `:hover` selectors are load-bearing, not defensive padding. The plain
   hover rule above is `… .cloud-chat-list li[data-id]:hover` — five
   specificity units against this rule's four — so WITHOUT them, moving the
   pointer over the conversation you are currently reading replaces its accent
   tint with the neutral hover wash, i.e. the open chat visibly loses its
   highlight exactly when you reach for it. (The nav rows and Admin links have
   no such problem: their hover and active selectors tie on specificity, so
   source order already keeps active on top.) Guarded by
   tests/test_ui_layout_theme.py::test_active_row_keeps_its_tint_on_hover. */
html[data-ui-layout="rail"] .rail-history .cloud-chat-list li.is-active,
html[data-ui-layout="rail"] .rail-history .cloud-chat-list li.active,
html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id].is-active:hover,
html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id].active:hover {
    background: var(--rail-active-bg);
    color: var(--ds-primary-dark);
    font-weight: 600;
}

html[data-ui-layout="rail"] .rail-history .cloud-chat-list-link:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: -2px;
}

/* Conversation label — grows to fill, truncates rather than wrapping. */
html[data-ui-layout="rail"] .rail-history .cloud-chat-list-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Live session status dot ──────────────────────────────────────────
   One 8px circle per conversation, written by js/chat_session_status.js and
   sitting between the title and the badges. Four states, and the motion is
   what separates them at a glance: `running` and `attention` pulse (something
   is happening, or waiting on you, RIGHT NOW), `done` and `error` are still
   (news about something that already ended).

   Colour alone never carries the meaning — each dot ships its own
   `role="img"` + aria-label ("Running" / "Needs your input" / "Finished" /
   "Failed"), and `attention` wears a ring the other three don't, so the one
   state that demands an action is distinguishable in greyscale.

   Scoped to the rail's history region like every other row part. Under topnav
   /chat the span still renders (one renderer, both chromes) but is unstyled
   and therefore zero-size — the label is announced, nothing is drawn. The
   COLLAPSED rail needs no rule of its own: `.rail-collapsible` hides this
   whole region, dot included. */
html[data-ui-layout="rail"] .rail-history .cloud-chat-list-status {
    flex-shrink: 0;
    width: 8px;
    height: 8px;
    border-radius: var(--ds-radius-pill);
    background: var(--ds-text-muted);
    pointer-events: none;
}

html[data-ui-layout="rail"] .rail-history .cloud-chat-list-status[data-state="running"] {
    background: var(--ds-primary);
    animation: cloud-chat-status-pulse 1.4s ease-in-out infinite;
}

html[data-ui-layout="rail"] .rail.rail-icon-mode #nav-chats[data-attention]::after {
    content: "";
    position: absolute;
    top: 6px;
    right: 6px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--ds-accent-warn-ink);
    box-shadow: 0 0 0 2px var(--ds-surface);
}

html[data-ui-layout="rail"] .rail-history .cloud-chat-list-status[data-state="attention"] {
    background: var(--ds-accent-warn-ink);
    box-shadow: 0 0 0 2px var(--ds-accent-warn-bg);
    animation: cloud-chat-status-pulse 2s ease-in-out infinite;
}

html[data-ui-layout="rail"] .rail-history .cloud-chat-list-status[data-state="done"] {
    background: var(--ds-accent-success-ink);
}

html[data-ui-layout="rail"] .rail-history .cloud-chat-list-status[data-state="error"] {
    background: var(--ds-accent-danger-ink);
}

@keyframes cloud-chat-status-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.35; }
}

/* A dot that breathes in the corner of the eye is exactly the motion this
   preference is about. The colours still say everything the animation did. */
@media (prefers-reduced-motion: reduce) {
    html[data-ui-layout="rail"] .rail-history .cloud-chat-list-status {
        animation: none;
    }
}

/* Origin pill (Slack) — small, non-interactive. */
html[data-ui-layout="rail"] .rail-history .cloud-chat-surface-badge {
    flex-shrink: 0;
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ds-text-muted);
    background: var(--ds-surface-dim);
    border-radius: 5px;
    padding: 1px 5px;
}

/* Paused badge reports an infra state the user can't act on — noise in the
   narrow rail; drop it (matches the retired history column's behavior). */
html[data-ui-layout="rail"] .rail-history .cloud-chat-paused-badge {
    display: none;
}

/* Pinned-state indicator — a static glyph, not a control (the pin ACTION lives
   in the row menu). Always visible and solid rather than outlined, so a pinned
   row stays identifiable once the "Pinned" header has scrolled away, by shape
   and not by color alone. */
html[data-ui-layout="rail"] .rail-history .cloud-chat-pin-flag {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    color: var(--ds-primary);
    pointer-events: none;
}

html[data-ui-layout="rail"] .rail-history .cloud-chat-pin-flag svg {
    width: 11px;
    height: 11px;
    fill: currentColor;
}

/* Row overflow trigger ("⋮") — one control for Pin/Rename/Delete, replacing the
   inline icon buttons that each cost width in a ~250px column. Hidden until the
   row is hovered or the control focused, and held open while its menu is. */
/* The row menu OVERLAYS the row's trailing edge instead of reserving 22px
   of every row for a button that is invisible until hover — with the pin flag
   that left titles 159px of a 221px row and a third of the column empty. On
   hover/focus the label yields the space (below), so the ellipsis moves under
   the button rather than the button covering letters. */
html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id] { position: relative; }
html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id]:hover .cloud-chat-list-label,
html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id]:focus-within .cloud-chat-list-label {
    padding-right: 24px;
}
html[data-ui-layout="rail"] .rail-history .chat-rowmenu-btn {
    position: absolute;
    right: 4px;
    top: 50%;
    transform: translateY(-50%);
    flex-shrink: 0;
    opacity: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: none;
    color: var(--ds-text-muted);
    cursor: pointer;
    line-height: 1;
    padding: 5px;
    border-radius: 6px;
}

html[data-ui-layout="rail"] .rail-history .chat-rowmenu-btn svg {
    width: 14px;
    height: 14px;
}

html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id]:hover .chat-rowmenu-btn,
html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id]:has(> .cloud-chat-list-link:focus-visible) .chat-rowmenu-btn,
html[data-ui-layout="rail"] .rail-history .chat-rowmenu-btn:focus-visible,
html[data-ui-layout="rail"] .rail-history .chat-rowmenu-btn[aria-expanded="true"] {
    opacity: 1;
}

html[data-ui-layout="rail"] .rail-history .chat-rowmenu-btn:hover,
html[data-ui-layout="rail"] .rail-history .chat-rowmenu-btn[aria-expanded="true"] {
    background: var(--ds-surface-dim);
    color: var(--ds-text-primary);
}

html[data-ui-layout="rail"] .rail-history .chat-rowmenu-btn:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: -2px;
}

@media (hover: hover) {
    html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id]:is(:hover, :focus-within) :is(.cloud-chat-pin-flag, .cloud-chat-list-status, .cloud-chat-surface-badge),
    html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id]:has(.chat-rowmenu-btn[aria-expanded="true"]) :is(.cloud-chat-pin-flag, .cloud-chat-list-status, .cloud-chat-surface-badge) {
        display: none;
    }
}

/* Coarse pointers get no hover event — pin the reveal-on-hover control on.
   (The menu PANEL itself is styled once, unscoped, in chat.css: it is
   body-appended, so it is not a descendant of .rail and cannot be reached by
   this file's selectors.) */
@media (hover: none) {
    html[data-ui-layout="rail"] .rail-history .chat-rowmenu-btn {
        opacity: 1;
    }
    html[data-ui-layout="rail"] .rail-history .cloud-chat-list li[data-id] {
        padding-right: 32px;
    }
    html[data-ui-layout="rail"] .rail-i-kbd,
    html[data-ui-layout="rail"] .agnes-search-esc,
    html[data-ui-layout="rail"] .agnes-search-foot {
        display: none;
    }
}

/* No date-group header rules here any more, and none should come back: neither
   renderer emits one under rail. The region is now the pinned shelf plus a
   RAIL_RECENT_LIMIT-row "Recent" feed under the Chats destination row (see
   rail_history.js), and at five rows a date boundary labels a boundary the list
   is too short to have — the last one standing, "Older", sat inside a section
   already labelled "Recent". The five buckets topnav still uses are styled by
   its own sheet; `_groupSessionsByDate` in chat.js is topnav-only now. */

/* Two rules used to live here and are deliberately gone with the truncation:
   an `li[hidden] { display: none }` restatement (the row/header rules above set
   an explicit `display`, which beat the UA `[hidden]`), and a margin patch for
   the collapsed case where a `hidden` date header sat between the pinned block
   and the first recent row. Nothing marks a row `hidden` any more — the only
   `[hidden]` left in the region is the empty-state <p> below — so both were
   dead weight describing a state that no longer exists. */

/* Empty state — hidden as soon as the list has any rows. */
html[data-ui-layout="rail"] .rail-history-empty {
    margin: 4px 0 0;
    padding: 6px 9px;
    font-size: 12.5px;
    color: var(--ds-text-muted);
}

html[data-ui-layout="rail"] .rail-history .cloud-chat-list:not(:empty) + .rail-history-empty {
    display: none;
}

html[data-ui-layout="rail"] .rail-history-all {
    display: inline-flex;
    align-items: center;
    min-height: 24px;
    margin-right: -6px;
    padding: 0 6px;
    border-radius: 6px;
    color: var(--ds-text-muted);
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    text-decoration: none;
}

html[data-ui-layout="rail"] .rail-history-all:hover {
    background: var(--rail-hover-bg);
    color: var(--ds-text-secondary);
}

html[data-ui-layout="rail"] .rail-history-all[aria-current="page"] {
    color: var(--ds-primary-dark);
    font-weight: 600;
}

html[data-ui-layout="rail"] .rail-history-all:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: var(--ds-focus-outline-offset);
}

html[data-ui-layout="rail"] .rail-admin {
    margin-top: 8px;
    flex-shrink: 0;
}

html[data-ui-layout="rail"] .rail:not(.rail-flat) .rail-nav-browse:not(:has(~ .rail-history)) {
    margin-bottom: auto;
}

html[data-ui-layout="rail"] .rail-i.rail-exit {
    background: transparent;
    color: var(--ds-primary-dark);
    font-weight: 600;
}
html[data-ui-layout="rail"] .rail-i.rail-exit:hover {
    background: color-mix(in srgb, var(--ds-primary) 7%, transparent);
    color: var(--ds-primary-dark);
}

html[data-ui-layout="rail"] .rail-admin-nav {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}
html[data-ui-layout="rail"] .rail-admin-nav__sections {
    display: flex;
    flex-direction: column;
    gap: var(--rail-nav-gap, 3px);
    flex: 1 1 auto;
    min-height: 0;
    margin-top: 6px;
    overflow-y: auto;
    --rail-fade: 20px;
    --rail-fade-top: 0px;
    --rail-fade-bottom: 0px;
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, black var(--rail-fade-top), black calc(100% - var(--rail-fade-bottom)), transparent 100%);
            mask-image: linear-gradient(to bottom, transparent 0, black var(--rail-fade-top), black calc(100% - var(--rail-fade-bottom)), transparent 100%);
}
html[data-ui-layout="rail"] .rail-admin-nav__sections.is-fade-top {
    --rail-fade-top: var(--rail-fade);
}
html[data-ui-layout="rail"] .rail-admin-nav__sections.is-fade-bottom {
    --rail-fade-bottom: var(--rail-fade);
}
html[data-ui-layout="rail"] .rail-admin-nav__label {
    margin: 10px 0 2px;
    padding: 0 var(--rail-row-pad-x) 0 var(--rail-content-x);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .04em;
    line-height: 1.6;
    text-transform: uppercase;
    color: var(--ds-text-muted);
}
html[data-ui-layout="rail"] .rail-admin-group__chev {
    width: 14px;
    height: 14px;
    flex: none;
    margin-left: auto;
    color: var(--ds-text-muted);
    transition: transform var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease);
}
html[data-ui-layout="rail"] .rail .rail-i svg.rail-admin-group__chev {
    width: 14px;
    height: 14px;
}
html[data-ui-layout="rail"] .rail-admin-group:not(.is-open) .rail-admin-group__chev {
    transform: rotate(-90deg);
}
html[data-ui-layout="rail"] .rail-admin-group__body {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 2px 0 6px;
}
html[data-ui-layout="rail"] .rail-admin-group__body[hidden] {
    display: none;
}
html[data-ui-layout="rail"] .rail-admin-group__link {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1px;
    min-height: 30px;
    padding: 5px var(--rail-row-pad-x) 5px var(--rail-label-indent);
    border-radius: 8px;
    color: var(--ds-text-secondary);
    font-size: 13px;
    font-weight: 500;
    line-height: 1.25;
    text-decoration: none;
}
html[data-ui-layout="rail"] .rail-admin-group__link:hover {
    background: var(--rail-hover-bg);
    color: var(--ds-text-primary);
}
html[data-ui-layout="rail"] .rail-admin-group__link.on {
    background: var(--rail-active-bg);
    color: var(--ds-primary-dark);
    font-weight: 600;
}
html[data-ui-layout="rail"] .rail-admin-group__link:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: var(--ds-focus-outline-offset);
}
html[data-ui-layout="rail"] .rail-admin-group__label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
html[data-ui-layout="rail"] .rail-admin-group__link.has-waiting {
    flex-direction: row;
    align-items: center;
    gap: 8px;
}
html[data-ui-layout="rail"] .rail-admin-group__link.has-waiting .rail-admin-group__label {
    flex: 1 1 auto;
    min-width: 0;
}
html[data-ui-layout="rail"] .rail-admin-group__link.on .rail-admin-waiting {
    background: var(--ds-surface);
    color: var(--ds-primary-dark);
}
html[data-ui-layout="rail"] .rail-admin-group.is-open .rail-admin-group__waiting {
    display: none;
}
html[data-ui-layout="rail"] .rail-admin-nav__sections .rail-i:focus-visible,
html[data-ui-layout="rail"] .rail-admin-nav__sections .rail-admin-group__link:focus-visible {
    outline-offset: -2px;
}
html[data-ui-layout="rail"] .rail-logo:focus-visible,
html[data-ui-layout="rail"] .rail-collapse-toggle:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: var(--ds-focus-outline-offset);
}
html[data-ui-layout="rail"] .rail-user:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: -2px;
}
html[data-ui-layout="rail"] .rail-admin-docs {
    position: relative;
    flex-shrink: 0;
    padding-top: 6px;
}
html[data-ui-layout="rail"] .rail-admin-docs__sr {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}
html[data-ui-layout="rail"] .rail-admin-docs__out {
    flex: none;
    margin-left: auto;
    padding-left: 10px;
    color: var(--ds-text-muted);
    font-size: 12px;
}

/* `.rail-flat` — the named-destinations layout (`named_browse_destinations`,
   app/switches.py). The conversation list is not rendered, so nothing grows
   between the two nav zones any more: the browse destinations follow the Chats
   row directly, behind the one divider a non-technical reader needs to see
   "chat" end and "places" begin. Admin follows Activity center directly, as
   one more row of the group the in-zone divider opens (`.rail-divider`, below)
   — so it drops its own border and top spacing and sits one row-gap under
   the row above. The foot keeps its `margin-top: auto`, so "Take {brand} to
   your tools", setup and the profile stay pinned to the bottom. The topbar
   block below resets both rules (a bar has no vertical slack and no room for
   a rule), so the flat class is inert there. */
html[data-ui-layout="rail"] .rail.rail-flat .rail-nav-browse {
    margin-top: 8px;
    border-top: 1px solid var(--ds-border);
    padding-top: 8px;
}
html[data-ui-layout="rail"] .rail.rail-flat .rail-admin {
    margin-top: var(--rail-nav-gap, 3px);
    border-top: 0;
    padding-top: 0;
}
/* The in-zone divider `.rail-flat` draws ahead of Activity center — a 1px
   rule inset to the row's text edge, sized by the same gap the rows use. */
html[data-ui-layout="rail"] .rail.rail-flat .rail-divider {
    height: 1px;
    flex-shrink: 0;
    background: var(--ds-border);
    margin: 5px 8px;
}


html[data-ui-layout="rail"] .rail-foot {
    margin-top: auto;
    padding-top: 6px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    flex-shrink: 0;
}

html[data-ui-layout="rail"] .rail-help {
    position: relative;
    flex-shrink: 0;
}

html[data-ui-layout="rail"] .rail-account {
    position: relative;
    display: flex;
    align-items: center;
    gap: 4px;
    border-top: 1px solid var(--ds-border);
    padding-top: 6px;
    margin-top: 2px;
}

html[data-ui-layout="rail"] .rail-account > .rail-user-menu {
    position: static;
    flex: 1 1 auto;
    min-width: 0;
}

/* The submenu affordance — quiet at rest, full strength while the row is
   hovered, focused or open, so it reads as part of the row rather than as a
   second thing in it. */
html[data-ui-layout="rail"] .rail-help-chev {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    margin-left: auto;
    color: var(--ds-text-muted);
    opacity: .7;
    transition: opacity var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease);
}

html[data-ui-layout="rail"] .rail-help .rail-i:hover .rail-help-chev,
html[data-ui-layout="rail"] .rail-help .rail-i:focus-visible .rail-help-chev,
html[data-ui-layout="rail"] .rail-help .rail-i[aria-expanded="true"] .rail-help-chev,
html[data-ui-layout="rail"] .rail-admin-docs .rail-i:hover .rail-help-chev,
html[data-ui-layout="rail"] .rail-admin-docs .rail-i[aria-expanded="true"] .rail-help-chev {
    opacity: 1;
}

/* The trigger holds the row hover while its panel is open, so it visibly owns
   what is hanging off it rather than reverting to rest the moment the pointer
   moves into the menu it opened. */
html[data-ui-layout="rail"] .rail-help .rail-i[aria-expanded="true"],
html[data-ui-layout="rail"] .rail-admin-docs .rail-i[aria-expanded="true"] {
    background: var(--rail-hover-bg);
    color: var(--ds-text-primary);
}

/* ── The menu ──────────────────────────────────────────────────────────────
   Opens UPWARD from the button, because there is nothing below it: this is the
   last row in a column pinned to the bottom of the viewport. Anchored to the
   zone's left edge so it lines up with the navigation it belongs to rather
   than with the 28px circle it hangs off.

   The collapsed variant lives in the icon-mode section: same panel, flowing
   right and up, since at 56px there is no column left to open over. */
html[data-ui-layout="rail"] .rail-help-panel {
    position: absolute;
    /* BESIDE the row and growing upward from it, which is what the row's `›`
       chevron promises. It opened upward-in-place first, anchored to the row's
       left edge — but that put a 232px panel directly over the onboarding card
       one row above, and that card reveals itself on `:hover` (see the gate on
       its panel): moving the pointer into this menu opened that one underneath
       it. Opening sideways takes the menu off the column entirely, so nothing
       in the rail is under the pointer while it is being read.

       Identical geometry collapsed — see the icon-mode rule — so the menu
       appears in the same place relative to its row at both rail widths. */
    left: calc(100% + 6px);
    right: auto;
    bottom: -6px;
    top: auto;
    width: 240px;
    max-height: calc(100vh - 120px);
    overflow-y: auto;
    padding: 6px;
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: 10px;
    box-shadow: var(--ds-shadow-lg);
    /* One scale for every rail popover — see `--rail-z-popover`. */
    z-index: var(--rail-z-popover);
}

html[data-ui-layout="rail"] .rail-help-panel[hidden] {
    display: none;
}

/* One row treatment for both element kinds in here — "Report a problem" and
   the two onboarding entries are <button>s (they open a dialog or reset state;
   there is no page to navigate to), the rest are links. A button inherits none
   of a link's text styling, so everything a row needs is stated once, here. */
html[data-ui-layout="rail"] .rail-help-item {
    display: flex;
    align-items: center;
    width: 100%;
    padding: 8px 10px;
    border: none;
    border-radius: 7px;
    background: none;
    color: var(--ds-text-primary);
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    line-height: 1.3;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

html[data-ui-layout="rail"] .rail-menu-ico {
    flex: none;
    width: 16px;
    height: 16px;
    color: var(--ds-text-secondary);
}

html[data-ui-layout="rail"] :is(.rail-help-item, .rail-user-menu .app-user-menu-item).has-ico {
    display: flex;
    align-items: center;
    gap: 10px;
}

html[data-ui-layout="rail"] :is(.rail-help-item, .rail-user-menu .app-user-menu-item).is-active .rail-menu-ico {
    color: inherit;
}

html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-item {
    padding: 8px 10px;
    border-radius: 7px;
    color: var(--ds-text-primary);
    font-size: 13px;
    font-weight: 500;
    line-height: 1.3;
}

html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-item:hover {
    background: var(--rail-hover-bg);
}

html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-item.is-active {
    background: var(--rail-active-bg);
    color: var(--ds-primary-dark);
    font-weight: 600;
}

html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-item:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: -2px;
}

html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-header {
    padding: 8px 10px 10px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--ds-border);
}

html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-name {
    overflow: hidden;
    color: var(--ds-text-primary);
    font-size: 13px;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
}

html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-email {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 1px;
    color: var(--ds-text-muted);
    font-size: 12px;
    font-weight: 400;
}

html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-role {
    flex: none;
    margin: 0;
    padding: 0 6px;
    border-radius: var(--ds-radius-pill, 999px);
    background: var(--ds-surface-sunken);
    color: var(--ds-text-secondary);
    font-size: 10px;
    font-weight: 600;
    line-height: 1.6;
}

html[data-ui-layout="rail"] .rail-user-menu .app-theme-toggle {
    padding: 4px 10px;
}

html[data-ui-layout="rail"] .rail-user-menu .app-theme-toggle-label {
    display: inline-flex;
    align-items: center;
    gap: 10px;
}

html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-item.is-state > span {
    display: flex;
    flex-direction: column;
}

html[data-ui-layout="rail"] body:has(.view-as-banner) .rail {
    top: 45px;
}

@media (hover: none) and (pointer: coarse) {
    html[data-ui-layout="rail"] #rail-shortcuts-open {
        display: none;
    }
}

html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-item.is-state {
    color: var(--ds-text-secondary);
}

html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-hint {
    color: var(--ds-text-muted);
    font-weight: 400;
}

html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-sep {
    margin: 4px 2px;
    background: var(--ds-border);
}

html[data-ui-layout="rail"] .rail-help-item:hover {
    background: var(--rail-hover-bg);
}

html[data-ui-layout="rail"] .rail-help-item:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: -2px;
}

/* You are already on the page this row names. Same accent the rail spends on
   an active destination — this is the same fact, told inside a menu. */
html[data-ui-layout="rail"] .rail-help-item.is-active {
    background: var(--rail-active-bg);
    color: var(--ds-primary-dark);
    font-weight: 600;
}

/* The one divider in the menu, between "something is wrong" and "I do not
   understand this yet". Two groups is the whole information architecture of
   this menu, so the line is load-bearing rather than decorative. */
html[data-ui-layout="rail"] .rail-help-sep {
    height: 1px;
    margin: 5px 4px;
    background: var(--ds-border);
}

html[data-ui-layout="rail"] .rail-user-menu {
    position: relative;
}

html[data-ui-layout="rail"] .rail-user {
    display: flex;
    align-items: center;
    gap: var(--rail-icon-gap);
    width: 100%;
    min-height: calc(var(--rail-row-h) + 2px);
    padding: 4px 8px;
    border-radius: 10px;
    border: none;
    background: none;
    cursor: pointer;
    text-align: left;
}

html[data-ui-layout="rail"] .rail-user:hover {
    background: var(--ds-surface-dim);
}

/* The one rail icon whose size wasn't already pinned. Every other leading icon
   in the column declares `flex-shrink: 0` (`.rail-i svg`, the onboarding ring,
   the brand orb); `.app-avatar` is a shared component from style-custom.css that
   the topnav header uses too, so the floor belongs here rather than on it.
   It matters because the collapsed rail keeps its labels IN FLOW now (hidden,
   not `display: none` — see the collapse section at the foot of this file): the
   profile row's flex line became avatar + gap + label against a ~38px content
   box, and the only shrinkable item was the avatar, which measured 19px instead
   of 36. Expanded there is room for all three, so this changes nothing there. */
html[data-ui-layout="rail"] .rail-user .app-avatar,
html[data-ui-layout="rail"] .rail-user .app-avatar-img {
    flex-shrink: 0;
    width: 24px;
    height: 24px;
    /* Circular, matching the brand ORB at the other end of the column. The
       reference this anatomy comes from uses rounded-square tiles at both ends;
       Agnes's mark is a sphere, so squaring the avatar to copy it would make
       the pair LESS consistent. Take the anatomy, not the radius. */
    border-radius: var(--ds-radius-pill);
    font-size: 10px;
}

html[data-ui-layout="rail"] .rail-user-txt {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1px;
    line-height: 1.25;
    min-width: 0;
    flex: 1 1 auto;
}

/* Line one — the name, with the role as a chip BESIDE it rather than under it.
   Under it was the old arrangement and it spent the block's second line on a
   word the menu header already carries; the email is the more useful thing to
   spend it on, because the question a bottom-of-column account block answers is
   "which account am I signed in as". */
html[data-ui-layout="rail"] .rail-user-name {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

html[data-ui-layout="rail"] .rail-user-txt b {
    font-weight: 500;
    font-size: 13px;
    color: var(--ds-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

html[data-ui-layout="rail"] .rail:not(.rail-icon-mode) .rail-user-txt {
    flex: 0 1 auto;
}

html[data-ui-layout="rail"] .rail-user-chev {
    flex: 0 0 auto;
    width: 14px;
    height: 14px;
    color: var(--ds-text-muted);
}

/* The one <button> in the profile menu ("Start over onboarding"). The shared
   `.app-user-menu-item` skin is written for <a>, so restate the bits a button
   doesn't inherit — otherwise it renders in the UA font, centred, and only as
   wide as its text, next to links that fill the panel. */
html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-btn {
    width: 100%;
    border: none;
    background: none;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}

/* The account menu opens BESIDE the column, growing upward from its row —
   the same geometry as the Help menu one row up and the Getting started
   panel between them. It opened upward IN PLACE for a long time, which put
   224px of menu over the conversation list; once its two neighbours had
   moved off the column, it was the one disclosure still covering the rail's
   own content. Same panel classes, repositioned. */
html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-panel {
    position: absolute;
    left: calc(100% + 6px);
    right: auto;
    bottom: -6px;
    top: auto;
    width: 240px;
    box-shadow: var(--ds-shadow-lg);
    z-index: var(--rail-z-popover);
}

/* ── Onboarding row + its popover ─────────────────────────────────────
   SAME anatomy as every other rail destination now (an icon + a label,
   `.rail-i`'s own treatment — hover wash, no standing tint, no card chrome):
   the only thing that sets it apart from Library/Agents is what the icon
   IS — a circular progress ring rather than a fixed glyph. That also means
   its height never differs between icon mode's collapsed/expanded states:
   the ring is a fixed size in both (collapsed, it's the whole row's
   content; expanded, it's the row's leading icon, same as any other row's
   svg), and only the LABEL appears/disappears — through the exact
   mechanism every other row's label does (`.rail-i-label`'s sibling in the
   shared show/hide list further down this file). */
html[data-ui-layout="rail"] .rail-getstarted {
    position: relative;
    flex-shrink: 0;
}

/* Retired at 5/5. The row exists to move someone through five steps, so
   with none left it is a full ring reporting a finished job, permanently,
   in the scarcest column in the app. Finishing the steps and clicking
   "Skip onboarding" (which lands all five at once) are deliberately the
   same state: the skip toast tells the caller the card is gone and where
   to find it again, and it has to be true. The profile menu's "Start over
   onboarding" is the way back to 0/5 — restartJourney() clears every step,
   which drops this class and brings the row back. */
html[data-ui-layout="rail"] .rail-getstarted.is-complete {
    display: none;
}

/* The row is a `.rail-i` and nothing more — the same anatomy as the admin's
   "Finish setup" row: the shared checklist glyph, a `.rail-i-label`, and a
   `.rail-i-count` of steps left. It had a progress ring and a two-line body of
   its own for a while; beside the setup row that read as two different kinds
   of thing, and a ring next to a word does not say "this opens" the way a row
   with a number does. Everything it needs comes from `.rail-i` and the count
   pill's rules above. */
html[data-ui-layout="rail"] .rail-getstarted-btn .rail-i-count:empty {
    /* An unreadable journey renders the count empty for the script to fill;
       an empty pill would be a dot. */
    display: none;
}

/* Dropdown card — opens UP from the launcher on hover / focus, and stays
   pinned when clicked open (.is-open). */
/* `:not(.rail-menu-open)` — the fix for a real overlap, not a precaution.
   The Help menu opens upward from a row directly BELOW this card, so its panel
   is painted over the card's own box. The card reveals on `:hover`, and hover
   does not care what is on top of it: moving the pointer into the open menu put
   it over the card underneath, which opened its popover at the same layer and
   painted it across the menu the caller was reading. Two cards over each other,
   from the other direction.
   js/rail_menu.js flags the rail while any surface is open, and the flag turns
   this reveal — and only this reveal — off for the duration. A menu that is
   open is the caller's focus; nothing behind it may open itself. */
/* CLICK only — the hover reveal is retired along with the popover.
   It made sense while this floated: hovering revealed something that cost the
   column nothing. In flow the disclosure PUSHES, so opening on hover means the
   sidebar re-lays itself out every time a pointer crosses one row on its way to
   the account block. A thing that changes the layout has to be asked for.
   (The `:not(.rail-menu-open)` gate that used to lead these selectors went with
   them: with no hover reveal there is nothing behind an open menu left to open
   itself. js/rail_menu.js still sets the flag — the Help panel's own overlap of
   this row is gone, but the flag is what proves it stays gone.) */
html[data-ui-layout="rail"] .rail-getstarted.is-open .rail-getstarted-panel,
html[data-ui-layout="rail"] .rail-getstarted.is-open .rail-getstarted-panel {
    display: block;
}

/* Explicit-close override. The toggle button lives INSIDE .rail-getstarted,
   so :focus-within stays true for as long as it (or the "×" the user just
   clicked) holds focus — and the cursor is, by definition, still over the
   launcher at the exact moment "×" or an outside-click handler fires. Both
   reveal rules above would therefore keep the panel visible regardless of
   what JS does to .is-open. `.is-closed` (added by rail_history.js's close
   paths and chat_onboarding.js's dismissJourney) forces it shut until the
   user's cursor actually leaves the launcher or they reopen it explicitly. */
html[data-ui-layout="rail"] .rail-getstarted.is-closed .rail-getstarted-panel {
    display: none !important;
}
/* ── The steps, expanded IN PLACE ──────────────────────────────────────────
   In flow beneath their own row, not floating above the column.

   It was an absolutely-positioned popover, and that one decision was the source
   of a whole class of problem: it covered the conversation list (so checking a
   six-step list meant hiding your work), it needed a z-index and therefore a
   place in a stacking order, and it sat over a hover target — which is how
   moving the pointer into the Help menu opened this card underneath it.

   In flow, none of those exist. The panel takes its space from the scroll
   region above it, which is the one part of this column that is designed to
   give space up, so the two fixed zones do not move and nothing overlaps
   anything. A disclosure that pushes is also simply more honest than one that
   floats: you can see what it cost you. */
html[data-ui-layout="rail"] .rail-getstarted-panel {
    /* Hidden by default via CSS (the `hidden` attribute was dropped so the
       `.is-open` rule above can reveal it — a `[hidden]` reset was winning
       otherwise). */
    display: none;
    /* BESIDE the column, growing upward from its row — the same geometry the
       Help menu uses one row above, so the two bottom-zone disclosures behave
       identically instead of one flying out and one pushing the column around.

       It was an in-flow sunken well for one build, and that was the wrong
       lesson drawn from a real bug. The bug was that the panel USED to open
       upward IN PLACE, over the column: it covered the conversation list and
       it sat on a hover target, which is how moving the pointer toward Help
       opened this card underneath it. Going in-flow did fix both, and paid for
       it out of the only flexible region in the column — opening the checklist
       cut the conversation list from six rows to four, so reading a step cost
       you a third of your working set. Opening SIDEWAYS fixes the same two
       things and costs the list nothing, because the panel is not in the
       column at all.

       The collapsed rail has done exactly this since it shipped (see the
       icon-mode rule, which now only has to restate the left offset) and its
       note already had the reason: out here it overlays the PAGE, not the
       sidebar's own content. */
    position: absolute;
    left: calc(100% + 6px);
    right: auto;
    bottom: -6px;
    width: 300px;
    margin-top: 0;
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: 12px;
    box-shadow: var(--ds-shadow-lg);
    padding: 6px;
    z-index: var(--rail-z-popover);
    /* Room for the whole list. In the column it was capped at 240px against
       299px of content, so "Skip onboarding" — the one control that makes the
       card go away — sat below a scroll line nobody had reason to look past.
       Out here the cap only has to keep the panel inside a short viewport. */
    max-height: min(70vh, 460px);
    overflow-y: auto;
}

/* The Journey card inside the popover.
   The .cloud-chat-journey-* styles live in chat.css, which only loads on
   /chat — so on every OTHER rail page (where the popover is mounted
   standalone) the card would render as raw UA-styled buttons. rail.css loads
   everywhere, so the full journey styling is (re)defined here, rail-scoped;
   on /chat these out-specify the chat.css base and keep the look identical. */
html[data-ui-layout="rail"] .rail-getstarted-panel .cloud-chat-journey {
    margin-top: 0;
    padding: 8px 6px;
    border-top: none;
    background: transparent;
}

html[data-ui-layout="rail"] .cloud-chat-journey-head {
    display: flex;
    align-items: center;
    /* Was a non-wrapping row: h3 + the "Complete ✓" badge + two icon buttons
       with no flex-wrap and no min-width:0 on the heading. In the completed
       state the badge appears and the row exceeds the panel's width (a fixed
       280px below 1024px, rail.css) — the panel's own `overflow-y: auto`
       computes overflow-x to auto too, so the icon buttons were clipped past
       the edge instead of just visible on their own line. */
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 4px 8px;
}

html[data-ui-layout="rail"] .cloud-chat-journey-head h3 {
    margin: 0;
    font-size: 14px;
    font-weight: 650;
    color: var(--ds-text-primary);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

html[data-ui-layout="rail"] .cloud-chat-journey-badge {
    font-size: 11px;
    color: var(--ds-accent-success-ink);
    background: var(--ds-accent-success-bg);
    border: 1px solid var(--ds-accent-success-line);
    border-radius: var(--ds-radius-pill, 999px);
    padding: 1px 8px;
}

html[data-ui-layout="rail"] .cloud-chat-journey-actions {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

html[data-ui-layout="rail"] .cloud-chat-journey-iconbtn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    color: var(--ds-text-muted);
    background: transparent;
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-pill, 999px);
    cursor: pointer;
    /* The visual pill stays 20px (a 44px button would dominate this compact
       header), but the actual hit area is expanded to the 44px touch-target
       minimum via an invisible ::before, not by growing the element itself. */
    position: relative;
}

html[data-ui-layout="rail"] .cloud-chat-journey-iconbtn::before {
    content: '';
    position: absolute;
    /* Full 44px vertical touch target, but horizontal expansion capped at 2px
       (half the 4px inter-button gap) so adjacent buttons' hit areas can't
       overlap. The rail head now renders only ↻ — the "×" is inline-only
       (chat_onboarding.js) — so nothing is adjacent to steal an edge here
       today; the cap stays as the rule for whatever lands beside it next. */
    inset: -12px -2px;
}

html[data-ui-layout="rail"] .cloud-chat-journey-iconbtn:hover {
    color: var(--ds-text-primary);
    border-color: var(--ds-text-muted);
}

/* "N of 5 done", in place of the slogan this line used to hold. No bar: the
   launcher card sits directly below this popover with its own, so a second one
   would draw the same fraction twice. */
html[data-ui-layout="rail"] .cloud-chat-journey-sub {
    margin: 2px 0 8px;
    font-size: 12px;
    color: var(--ds-text-muted);
}

html[data-ui-layout="rail"] .cloud-chat-journey-list {
    display: flex;
    flex-direction: column;
    gap: 1px;
}

/* mark · (label + why). No trailing chevron: one column of glyphs down the left
   is enough, and a second down the right edge crowded labels that already wrap at
   this width. Top-aligned, not centered — with the row centered a two-line label
   floated its ✓/→/• to the middle of the text block, so the marks stopped lining
   up with each other and the list lost its left spine. */
html[data-ui-layout="rail"] .cloud-chat-journey-step {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    width: 100%;
    text-align: left;
    padding: 7px 8px;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: var(--ds-text-secondary);
    font-size: 13.5px;
    line-height: 1.35;
    font-family: inherit;
    cursor: pointer;
}

html[data-ui-layout="rail"] .cloud-chat-journey-step:hover {
    background: var(--ds-surface-dim);
}

html[data-ui-layout="rail"] .cloud-chat-journey-step:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: var(--ds-focus-outline-offset);
}

html[data-ui-layout="rail"] .cloud-chat-journey-mark {
    flex-shrink: 0;
    width: 16px;
    text-align: center;
    font-weight: 600;
}

html[data-ui-layout="rail"] .cloud-chat-journey-body {
    flex: 1;
    min-width: 0;
}

html[data-ui-layout="rail"] .cloud-chat-journey-label {
    display: block;
}

/* The step's own explanation, on the suggested step only — it replaced a `title`
   tooltip that needed a hover nobody tries and opened an OS box across the rows
   below it. */
html[data-ui-layout="rail"] .cloud-chat-journey-why {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    line-height: 1.4;
    color: var(--ds-text-muted);
}

/* Done: ✓ + muted label, no line-through — struck text on a label that wraps to
   two lines at this width read as damaged rather than finished, and it repeated
   what the tick already says. */
html[data-ui-layout="rail"] .cloud-chat-journey-step.done {
    color: var(--ds-text-muted);
}

html[data-ui-layout="rail"] .cloud-chat-journey-step.done .cloud-chat-journey-mark {
    color: var(--ds-accent-success-ink);
}

/* The suggested step, and the card's only focal point: tinted block, primary
   ink, its own why-line. A suggestion of where to resume — not a gate, since
   every other row is equally clickable. */
html[data-ui-layout="rail"] .cloud-chat-journey-step.active {
    color: var(--ds-text-primary);
    background: var(--ds-primary-light);
    font-weight: 600;
}

html[data-ui-layout="rail"] .cloud-chat-journey-step.active:hover {
    background: var(--ds-primary-light);
}

html[data-ui-layout="rail"] .cloud-chat-journey-step.active .cloud-chat-journey-mark {
    color: var(--ds-primary);
}

html[data-ui-layout="rail"] .cloud-chat-journey-step.active .cloud-chat-journey-why {
    font-weight: 400;
}

/* A step further down the list — still a real control (pointer cursor + hover
   wash from the base rule above): every step is clickable in any order. Held at
   the row default so all three ranks stay distinct — `.active` darkest, this one
   secondary, `.done` muted. */
html[data-ui-layout="rail"] .cloud-chat-journey-step.todo {
    color: var(--ds-text-secondary);
}

/* A step's secondary destination ("Choose where Agnes reaches you" under "Use
   Agnes outside this tab"). Restated in FULL, not just sized: the base rule lives
   in chat.css, which does not load on the other rail pages, so on every page but
   /chat this rendered as a raw browser link — blue, or purple once visited,
   underlined, in a card built entirely from `--ds-*` ink. Geometry (the indent
   that hangs it off the label rather than the ✓/→ gutter) matches the base rule;
   the size is 12px because the rail sets its step rows to 13.5px rather than the
   token scale, so --text-xs would read as fine print rather than one step down. */
html[data-ui-layout="rail"] .cloud-chat-journey-sub-action {
    display: block;
    margin: -1px 0 2px 32px;
    padding: 2px 0;
    font-size: 12px;
    line-height: 1.45;
    color: var(--ds-text-muted);
    text-decoration: none;
}

html[data-ui-layout="rail"] .cloud-chat-journey-sub-action:hover {
    color: var(--ds-primary);
    text-decoration: underline;
}

html[data-ui-layout="rail"] .cloud-chat-journey-sub-action:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: var(--ds-focus-outline-offset);
}

html[data-ui-layout="rail"] .cloud-chat-journey-sub-action.is-linked {
    color: var(--ds-accent-success-ink);
}

/* Panel footer: the quiet skip. Restated here for the same reason as everything
   above — chat.css doesn't load on the other rail pages, and the popover is
   mounted on all of them. */
html[data-ui-layout="rail"] .cloud-chat-journey-finish {
    display: block;
    width: 100%;
    margin-top: 10px;
    padding: 5px 12px;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: var(--ds-text-muted);
    font: inherit;
    font-size: 12px;
    cursor: pointer;
}

html[data-ui-layout="rail"] .cloud-chat-journey-finish:hover {
    color: var(--ds-text-primary);
    background: var(--rail-hover-bg, var(--ds-surface));
}

html[data-ui-layout="rail"] .cloud-chat-journey-finish:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: var(--ds-focus-outline-offset);
}

/* ── Collapsed rail — a persisted USER PREFERENCE ────────────────────
 * Gated to `min-width: 1025px`, deliberately never touching anything
 * inside the `max-width: 1024px` query below: that query already turns the
 * rail into a wrapping top bar with its own collapse story, and letting
 * icon-mode's rules leak in there — even harmlessly — is exactly the kind
 * of interaction CLAUDE.md's design-system contract warns against ("don't
 * let icon mode fight the narrow-screen reflow"). Keeping the two
 * breakpoints mutually exclusive sidesteps any need to reason about which
 * one wins on specificity. */
@media (min-width: 1025px) {
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) .rail-account {
        padding-right: 8px;
    }

    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) .rail-account > .rail-help {
        order: 2;
        flex: 0 0 26px;
        width: 26px;
    }

    html[data-ui-layout="rail"] .rail.rail-topbar .rail-account > .rail-bell,
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) .rail-account > .rail-bell {
        order: 1;
        position: relative;
        flex: 0 0 26px;
        width: 26px;
        max-width: none;
        height: 26px;
        min-height: 26px;
        padding: 0;
        justify-content: center;
        border-radius: var(--ds-radius-pill, 999px);
    }

    html[data-ui-layout="rail"] .rail.rail-topbar .rail-bell svg,
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) .rail-bell svg {
        width: 17px;
        height: 17px;
    }

    html[data-ui-layout="rail"] .rail.rail-topbar .rail-bell .rail-i-label,
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) .rail-bell .rail-i-label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    html[data-ui-layout="rail"] .rail.rail-topbar .rail-bell .rail-i-attn,
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) .rail-bell .rail-i-attn {
        position: absolute;
        top: -5px;
        right: -4px;
        margin: 0;
        min-width: 16px;
        height: 16px;
        padding: 0 4px;
        font-size: 10px;
        line-height: 16px;
        box-shadow: 0 0 0 2px var(--ds-surface);
    }

    html[data-ui-layout="rail"] .rail.rail-topbar .rail-bell .rail-i-dot,
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) .rail-bell .rail-i-dot {
        position: absolute;
        bottom: -2px;
        right: -2px;
        width: 8px;
        height: 8px;
        border-radius: var(--ds-radius-pill);
        background: var(--ds-primary);
        box-shadow: 0 0 0 2px var(--ds-surface);
    }

    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) .rail-help-trigger {
        max-width: none;
        width: 26px;
        height: 26px;
        min-height: 26px;
        padding: 0;
        justify-content: center;
        border-radius: var(--ds-radius-pill, 999px);
    }

    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) .rail-help-trigger svg {
        width: 17px;
        height: 17px;
    }

    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) .rail-help-trigger .rail-i-label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) .rail-logo-row > .rail-logo {
        margin-right: auto;
    }
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) .rail-search-icon {
        display: inline-flex;
    }
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) .rail-account .app-user-menu-panel {
        left: 0;
        right: 0;
        bottom: calc(100% + 6px);
        width: auto;
        min-width: 0;
    }
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) #rail-search-trigger {
        display: none;
    }
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar) :is(.rail-toggle, .rail-help-trigger) {
        position: relative;
    }
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar):not(.rail-menu-open) :is(.rail-toggle, .rail-help-trigger, .rail-bell):is(:hover, :focus-visible) > .rail-icon-tip {
        display: block;
        position: absolute;
        right: 0;
        z-index: var(--rail-z-chip);
        width: max-content;
        padding: 5px 9px;
        border-radius: var(--ds-radius-btn, 9px);
        background: var(--ds-text-primary);
        color: var(--ds-text-inverse);
        box-shadow: var(--ds-shadow-md);
        font-size: 12px;
        font-weight: 500;
        line-height: 1.25;
        white-space: nowrap;
        pointer-events: none;
    }
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar):not(.rail-menu-open) .rail-toggle:is(:hover, :focus-visible) > .rail-icon-tip {
        top: calc(100% + 6px);
    }
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar):not(.rail-menu-open) :is(.rail-help-trigger, .rail-bell):is(:hover, :focus-visible) > .rail-icon-tip {
        bottom: calc(100% + 6px);
    }
    html[data-ui-layout="rail"] .rail:not(.rail-icon-mode):not(.rail-topbar):not(.rail-menu-open) :is(.rail-toggle, .rail-help-trigger):is(:hover, :focus-visible) > .rail-icon-tip[data-shortcut-text]::after {
        content: attr(data-shortcut-text);
        margin-left: 8px;
        font-weight: 400;
        opacity: .72;
    }
}

@media (min-width: 1025px) {
    /* The width transition belongs to BOTH states, not just the collapsed one.
       It used to be declared only on `.rail.rail-icon-mode` below, and a
       transition can only run when the element it is declared on still matches
       AFTER the change: clicking the toggle to EXPAND removes that class, so
       the rule carrying the transition vanished in the same frame the width
       changed and the rail snapped 56 -> 240px with no animation at all, while
       collapsing (which ADDS the class) animated normally. Declaring it on the
       base `.rail` gives the expanded state its own transition, so the toggle
       animates in both directions.

       Desktop-gated even though `.rail` itself is not, so it cannot animate
       the ≤1024px top-bar reflow on a viewport resize — the two breakpoints
       stay mutually exclusive (see the note above). */
    html[data-ui-layout="rail"] .rail {
        transition: width var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease) 0s,
                    box-shadow var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease) 0s;
    }

    html[data-ui-layout="rail"] body:has(.rail) {
        transition: padding-left var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease);
    }

    html[data-ui-layout="rail"] body:has(.rail.rail-icon-mode) {
        /* Overrides the plain `body:has(.rail)` 240px reservation above —
           `:has(.rail.rail-icon-mode)` carries the extra class in its
           argument, so it is unconditionally more specific and wins
           regardless of source order. */
        padding-left: 56px;
        /* …and the fixed chrome that starts at the same edge moves with it. */
        --rail-clearance: 56px;
    }

    html[data-ui-layout="rail"] .rail.rail-icon-mode {
        width: 56px;
        padding: 16px 8px;
        transition: width var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease) 0s,
                    box-shadow var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease) 0s;
    }

    /* Collapsed contents: brand mark, the destination rows (onboarding
       included — its icon is a progress ring rather than a fixed glyph, see
       the dedicated section below, but it is otherwise an ordinary row) and
       the profile avatar — the label TEXT beside each icon is what this
       hides, nothing that needs text width to read.
       `.rail-getstarted-body` is this row's label (title + "N of 5 steps
       complete", two lines rather than `.rail-i-label`'s one) — same
       mechanism, its own selector because it isn't `.rail-i-label` itself.

       The CONVERSATION REGION is hidden here too, and it is the one entry in
       this list that is not merely a label: its rows are text end to end and
       have no icon form to collapse to. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-txt,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-full,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-i-label,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-user-txt,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-i-kbd,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-help-chev,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-history {
        opacity: 0;
        visibility: hidden;
        transition: opacity var(--rail-peek-text-out) var(--ds-ease-standard, ease) 0s,
                    visibility 0s linear var(--rail-peek-text-out);
    }

    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-i-label {
        visibility: visible;
    }

    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-mark {
        opacity: 1;
        visibility: visible;
        transition: opacity var(--rail-peek-text-out) var(--ds-ease-standard, ease) 0s,
                    visibility 0s linear 0s;
    }

    /* ── The Chats row: this zone's COLLAPSED form ────────────────────
       The conversation region above is text end to end and has no icon form, so
       the 56px strip cannot show it — `.rail-history` is in the hide list above
       for exactly that reason. `#nav-chats` is what the strip shows instead: one
       speech-bubble glyph standing in for the whole zone while the rail is
       narrow, folded away again the moment the rail opens and the real lists
       take over. The two are exact
       complements, so the zone reads as ONE thing at either width and never as
       an icon row stacked above the lists that repeat it.

       Folded with HEIGHT, not `visibility` — the opposite choice from the labels
       above, and for the opposite reason. `visibility: hidden` keeps a box in
       flow, which is precisely what the hide list wants (nothing moves; the
       column just grows around content that was always there). Here the box
       must GO: an expanded rail with a `visibility: hidden` row above the lists
       would carry a permanent 34px hole between New chat and Pinned. So height
       (plus padding and the icon's own opacity) interpolates from the row's full
       size to zero, which animates cleanly and leaves no gap at either end.

       `overflow: hidden` clips the glyph while the row is mid-fold, and
       `visibility` still steps at the far end of the transition so a folded row
       takes no clicks and holds no tab stop.

       The FOLDED default is not here — it is outside this media query, up with
       the other `.rail-i` rules, because it has to hold below 1025px too. Down
       there the rail is a bar with no icon mode at all, so a default that lived
       in here would leave the row unstyled and this zone would show the stand-in
       AND the lists it stands in for. These two rules are the icon-strip
       exception to that default. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-i--collapsed-only {
        height: var(--rail-row-h);
        min-height: var(--rail-row-h);
        /* Unfolded, it is an ordinary row again and wants the ordinary gap. */
        margin: 0;
        padding-top: 0;
        padding-bottom: 0;
        opacity: 1;
        visibility: visible;
        transition: height var(--rail-peek-text-out) var(--ds-ease-standard, ease) 0s,
                    padding var(--rail-peek-text-out) var(--ds-ease-standard, ease) 0s,
                    opacity var(--rail-peek-text-out) var(--ds-ease-standard, ease) 0s,
                    visibility 0s linear 0s;
    }

    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-user-txt {
        overflow: hidden;
    }

    html[data-ui-layout="rail"] .rail.rail-icon-mode:not(.rail-menu-open):not(.rail-chip-hidden):not(:has(.rail-getstarted.is-open)) .rail-i:hover .rail-i-label[data-hint]::after,
    html[data-ui-layout="rail"] .rail.rail-icon-mode:not(.rail-menu-open):not(.rail-chip-hidden):not(:has(.rail-getstarted.is-open)):not(:hover) .rail-i:focus-visible .rail-i-label[data-hint]::after {
        content: " · " attr(data-hint);
        font-weight: 400;
    }
    html[data-ui-layout="rail"] .rail.rail-icon-mode:not(.rail-menu-open):not(.rail-chip-hidden):not(:has(.rail-getstarted.is-open)) .rail-i:hover .rail-i-label[data-shortcut-text]::after,
    html[data-ui-layout="rail"] .rail.rail-icon-mode:not(.rail-menu-open):not(.rail-chip-hidden):not(:has(.rail-getstarted.is-open)):not(:hover) .rail-i:focus-visible .rail-i-label[data-shortcut-text]::after,
    html[data-ui-layout="rail"] .rail.rail-icon-mode:not(.rail-menu-open):not(.rail-chip-hidden) .rail-toggle:hover .rail-toggle-tip[data-shortcut-text]::after,
    html[data-ui-layout="rail"] .rail.rail-icon-mode:not(.rail-menu-open):not(.rail-chip-hidden):not(:hover) .rail-toggle:focus-visible .rail-toggle-tip[data-shortcut-text]::after {
        content: attr(data-shortcut-text);
        margin-left: 8px;
        font-weight: 400;
        opacity: .72;
    }
    html[data-ui-layout="rail"] .rail.rail-icon-mode:not(.rail-menu-open):not(.rail-chip-hidden) .rail-toggle:hover .rail-toggle-tip,
    html[data-ui-layout="rail"] .rail.rail-icon-mode:not(.rail-menu-open):not(.rail-chip-hidden):not(:hover) .rail-toggle:focus-visible .rail-toggle-tip {
        display: block;
        white-space: nowrap;
    }
    html[data-ui-layout="rail"] .rail.rail-icon-mode:not(.rail-menu-open):not(.rail-chip-hidden) .rail-toggle:hover .rail-toggle-tip,
    html[data-ui-layout="rail"] .rail.rail-icon-mode:not(.rail-menu-open):not(.rail-chip-hidden):not(:hover) .rail-toggle:focus-visible .rail-toggle-tip,
    html[data-ui-layout="rail"] .rail.rail-icon-mode:not(.rail-menu-open):not(.rail-chip-hidden):not(:has(.rail-getstarted.is-open)) .rail-i:hover .rail-i-label,
    html[data-ui-layout="rail"] .rail.rail-icon-mode:not(.rail-menu-open):not(.rail-chip-hidden):not(:has(.rail-getstarted.is-open)):not(:hover) .rail-i:focus-visible .rail-i-label {
        position: fixed;
        left: 62px;
        top: var(--rail-chip-y, 50%);
        transform: translateY(-50%);
        z-index: var(--rail-z-chip);
        flex: 0 0 auto;
        width: max-content;
        max-width: 260px;
        overflow: hidden;
        text-overflow: ellipsis;
        padding: 5px 9px;
        border-radius: var(--ds-radius-btn, 9px);
        background: var(--ds-text-primary);
        color: var(--ds-text-inverse);
        box-shadow: var(--ds-shadow-md);
        font-size: 12px;
        font-weight: 500;
        line-height: 1.25;
        pointer-events: none;
        opacity: 1;
        visibility: var(--rail-chip-visibility, hidden);
        transition: none;
    }

    /* Reduced motion: the stand-in SWAPS for the lists instead of folding.
       Nested here, at the end of the strip's own rules, because the two
       icon-mode selectors above carry a `.rail.rail-icon-mode` compound that
       outranks the base `.rail` one — an opt-out written up beside the other
       reduced-motion rules would simply lose to them. `visibility` keeps its 0s
       step in every state so a folded row stays inert; only the interpolation
       goes. */
    @media (prefers-reduced-motion: reduce) {
        html[data-ui-layout="rail"] .rail .rail-i--collapsed-only,
        html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-i--collapsed-only {
            transition: visibility 0s linear 0s;
        }
    }

    /* Centre the icon in the narrow column now that its label is hidden — as
       PADDING, not as `justify-content: center`.
       Both express the same resting position; only one of them can animate.

       The four values are one formula, not four guesses. The strip is 56px wide
       with a 1px right border, so its centre is 27.5px; the rail's own padding
       puts a row's content edge at 8px. Centring an icon therefore wants
       `padding-left: 19.5px - iconWidth / 2`, and the rail has four icon sizes:

         .rail-logo          30px orb     -> 4.5px
         .rail-i             18px glyph   -> 10.5px
         .rail-getstarted    28px ring    -> 5.5px

       Verified against the measured `justify-content: center` layout this
       replaces: every icon centred at cx 27.5 before and after. If an icon's
       size changes, its value here changes with it — which is the one thing
       `justify-content` did for free, and the reason each line names its size.

       `padding-right` stays 0 while collapsed for the same reason it always
       did; the labels are clipped either way, and it animates back with the
       rest. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-i,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-user {
        padding-right: 0;
        transition: padding var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease) 0s,
                    gap var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease) 0s;
    }

    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo { padding-left: 4.5px; }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-i { padding-left: 10.5px; }
    /* The setup count in the strip: taken out of the flex flow (the hidden
       label still occupies it) and anchored to the row's trailing edge, at
       mid-height, beside the icon. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-i { position: relative; }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-i-count {
        position: absolute;
        right: 0;
        top: 50%;
        transform: translateY(-50%);
        min-width: 15px;
        padding: 0 4px;
        font-size: 10px;
        line-height: 15px;
        transition: right var(--ds-motion-fast) var(--ds-ease-standard),
                    font-size var(--ds-motion-fast) var(--ds-ease-standard);
    }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-user { padding-left: 7.5px; }
    /* Collapsed there is no column to expand INTO — 56px cannot hold a step
       list — so the panel goes back to being a flyout beside the strip, and
       takes its floating chrome back with it. This is the one state where the
       popover treatment is right, and it is right for the reason the expanded
       state is not: here it overlays the page, not the sidebar's own content. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-getstarted-panel {
        /* Only the offset differs now — the expanded rail opens the same panel
           the same way, so everything else is inherited from the base rule.
           14, not the base's 6: the collapsed row's content box ends at 48px,
           so `+6` would land the panel's left edge at 54, three pixels inside
           the strip's own 55/56px border — a popover standing *in* the column
           it is supposed to stand beside. `+14` lands at 62, which is where the
           Help menu and the chat flyout open too, so all three share one edge. */
        left: calc(100% + 14px);
        bottom: 0;
    }

    /* ── The `?` menu, flown out beside the strip ───────────────────────────
       Same problem, same answer as the onboarding panel above, and the same
       idiom as the label chip: the strip stays 56px and the content comes out
       beside it rather than crushing itself into it. A 240px menu anchored
       `left: 0` to a 40px row would render as a ribbon of clipped labels over
       the icon column.

       `bottom: 0` aligns the panel's foot with the button's own row, so it
       reads as hanging off that control rather than floating near it — and it
       grows UPWARD from there, which is the same direction it opens in the
       expanded rail. One gesture, one direction, two widths. */
    /* Same side, a touch further out: the collapsed row's content box ends at
       48px, so the expanded `+6` would land the panel under the strip's own
       55/56px border. `+14` clears it and matches where the chat flyout opens,
       so the two popovers share one left edge. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-help-panel {
        /* 14, not the chip's 6: this row's content box ends at 48px, so `+6`
           put the panel's left edge at 54 — under the strip's own 55/56px
           border, i.e. a menu that starts three pixels inside the column it is
           supposed to be standing beside. `+14` lands it at 62, which is where
           the chat flyout opens too, so the two popovers share one left edge. */
        left: calc(100% + 14px);
        right: auto;
        bottom: 0;
        width: 248px;
    }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-user-chev {
        opacity: 0;
        visibility: hidden;
    }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-account {
        flex-direction: column;
        align-items: stretch;
        gap: 2px;
    }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-account > .rail-bell { order: 1; }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-account > .rail-help { order: 2; }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-account > .rail-user-menu { order: 3; }
    /* The account menu flies out the same way, at the same left edge. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-user-menu .app-user-menu-panel {
        left: calc(100% + 14px);
        right: auto;
        bottom: 0;
    }

    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-history.is-open .rail-history-panel {
        position: fixed;
        left: 62px;
        /* `top` is written by js/rail_menu.js from the trigger's own rect, so
           the panel opens level with the bubble that opened it and stays level
           with it however the rows above change height (the search box, the
           brand lockup, a wrapped label). A CSS constant here would have been a
           guess at the sum of five rows, wrong the first time any of them moved.
           `bottom: auto` so the panel is sized by its content, not stretched
           between two edges. */
        bottom: auto;
        /* Never taller than the viewport it hangs in. The list inside scrolls;
           this only bounds the panel. */
        max-height: min(560px, calc(100vh - 32px));
        width: 264px;
        padding: 8px;
        background: var(--ds-surface);
        border: 1px solid var(--ds-border);
        border-radius: 12px;
        box-shadow: var(--ds-shadow-lg);
        z-index: var(--rail-z-popover);
        opacity: 1;
        visibility: visible;
    }

    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-history.is-open .rail-history-foot {
        display: flex;
    }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-history.is-open #rail-history-panel .rail-history-all {
        display: none;
    }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-chatsec.is-collapsed .rail-chatsec-body {
        display: block;
    }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-chatsec-toggle {
        pointer-events: none;
    }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-chatsec-chev {
        display: none;
    }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-chatsec-hd {
        margin-top: 4px;
    }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-chatsec-toggle[data-attention]::after {
        content: none;
    }

    /* The scroll body is sized by the RAIL's free space in the column — `flex:
       1 1 0`, i.e. "take exactly what the column leaves you". In the flyout
       there is no column leaving it anything: the panel is a
       content-sized box, so a zero flex-basis resolved to zero height and the
       panel opened as a 58px sliver holding nothing but the New chat row, with
       six conversations scrolled out of a box the height of one.
       `flex: 0 1 auto` is the flyout's version of the same intent — size to the
       rows, shrink (and scroll) only once the panel's own max-height is hit. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-history.is-open .rail-history-body {
        flex: 0 1 auto;
        max-height: calc(min(560px, 100vh - 32px) - 70px);
    }

    /* `.rail-history` clips its own overflow so the body scrolls inside it
       rather than spilling into the zone below. In the flyout the body is no
       longer inside it — but the section is still the panel's ancestor, and a
       `position: fixed` child only escapes that clip while nothing between it
       and the viewport establishes a containing block. Dropping the clip for
       the duration removes the question entirely; there is nothing left in the
       section to clip while its contents are flown out. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-history.is-open {
        overflow: visible;
        /* The collapsed section is parked at `opacity: 0; visibility: hidden`.
           `visibility` the panel can override — it is inherited, and a visible
           child of a hidden parent paints. `opacity` it CANNOT: a parent at 0
           composites its whole subtree away, and no child declaration reaches
           back through that. So the section goes to 1 here and the panel keeps
           its own `visibility: visible`; the section itself has no background,
           border or text of its own, so the only thing that becomes visible is
           the panel — which is the intent — and the section stays hidden and
           inert exactly as before.
           (Found by measuring: the flyout opened at the right place, the right
           size and with the right six rows, and painted nothing at all.) */
        opacity: 1;
    }

    /* The trigger holds the hover state while its panel is open, like every
       other launcher in the column. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode [data-chats-flyout-trigger][aria-expanded="true"] {
        background: var(--rail-hover-bg);
        color: var(--ds-text-primary);
    }

    /* The `?`'s own chip is NOT in this collapsed-only section — it is a
       top-level rule beside the Help row, because unlike every other row's
       it has to paint at BOTH rail widths. See the note there. */

    /* The logo row shrinks to a single ~40px slot when collapsed — position
       the anchor for the toggle below. Its padding animates with every other
       row's. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-row {
        position: relative;
        padding-left: 0;
        padding-right: 0;
        transition: padding var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease) 0s;
    }

    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-toggle {
        transition: opacity var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease);
    }

    /* `#rail-toggle` is absolutely positioned rather than sitting beside the orb
       — there is no room for both at 56px — and stays invisible until
       hover/focus reveals it, so "only the orb shows" at rest.

       Touch keeps the centred variant — see the `hover: none` query below, where
       the toggle is visible at rest and has no animation to be part of. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-toggle {
        position: absolute;
        /* The row's own content band — see `--rail-logo-row-pt/pb` at the top
           of this file for why this is not `top: 0; bottom: 0`. */
        top: var(--rail-logo-row-pt);
        bottom: var(--rail-logo-row-pb);
        /* Centred over the icon column at 56px: the strip's rows put their
           glyphs on one vertical line, and a control that appears in the orb's
           slot has to sit on that line too. `right: 0` parked it against the
           strip's edge, half a glyph to the right of everything else, which
           read as misaligned the moment it faded in. (6.5px: the row is the
           rail's 56px less 8px padding a side, 39px wide, and the control 26 —
           so 6.5px each side centres it.) */
        right: 6.5px;
        left: auto;
        margin: auto 0;
        opacity: 0;
        pointer-events: none;
    }

    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-row:hover .rail-toggle,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-row:focus-within .rail-toggle {
        opacity: 1;
        pointer-events: auto;
        transition: opacity var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease) var(--rail-peek-text-delay);
    }

    /* `:not(:focus-within)` on the logo itself, because the row-level
       `:focus-within` above matches when the focused element IS the logo:
       tabbing to Home hid Home, leaving focus on something invisible and
       unclickable, and the next visible control only reachable by tabbing
       past it (Devin Review on #2074). The orb gives way to the toggle when
       the pointer or the keyboard is somewhere else in the row — never when
       the keyboard is on the orb. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-row:hover .rail-logo:not(:focus-within),
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-row:focus-within .rail-logo:not(:focus-within) {
        opacity: 0;
        pointer-events: none;
    }

}

/* No transition, full stop — combined (not nested) with the desktop-only
   gate above so this can't accidentally apply inside the ≤1024px query. */
@media (min-width: 1025px) and (prefers-reduced-motion: reduce) {
    /* The base `.rail` + `body` pair that carries the toggle's own animation
       (declared at the top of the desktop query above), not just the collapsed
       state — otherwise expanding still glided while everything else jumped. */
    html[data-ui-layout="rail"] .rail,
    html[data-ui-layout="rail"] body:has(.rail),
    html[data-ui-layout="rail"] .rail.rail-icon-mode,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-toggle,
    /* The rows whose padding now carries the icons to their expanded position,
       and the logo row's alongside them — with motion reduced the width jumps
       straight to 240px, so an icon that still travelled would arrive after the
       column it sits in. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-i,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-user,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-row,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-txt,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-i-label,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-user-txt,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-help-chev,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-history,
    /* Both halves of the lockup ⇄ mark swap, for the same reason: with motion
       reduced the two exchange places on one frame rather than cross-fading. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-full,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-mark,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-row:hover .rail-toggle,
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo-row:focus-within .rail-toggle {
        transition: none;
    }
}

/* Touch (`hover: none`) can never trigger the reveal-on-hover swap above —
 * there is no hover event to fire it — so the toggle defaults to visible
 * there instead of waiting for a gesture that cannot happen. This is what
 * replaces the old dedicated `#rail-icon-toggle` tap affordance: one
 * control, shown unconditionally here rather than a second one bolted on
 * for touch alone. */
@media (min-width: 1025px) and (hover: none) {
    /* Centred in the slot, not right-anchored. The desktop rule pins it to the
       row's right edge so it can ride the widening panel instead of teleporting
       — but that is a choreography concern, and there is no choreography here:
       this toggle is visible at REST in a 56px strip, with the orb hidden behind
       it, so the middle of the slot is the only place it belongs. */
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-toggle {
        /* Same content band as the desktop rule — only the horizontal
           anchoring differs. */
        top: var(--rail-logo-row-pt);
        bottom: var(--rail-logo-row-pb);
        right: auto;
        left: 0;
        margin: auto;
        opacity: 1;
        pointer-events: auto;
    }

    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-logo {
        opacity: 0;
        pointer-events: none;
    }
}

/* Small + tablet screens: the fixed column becomes a normal top block so
   content isn't squeezed. Raised from the prototype's 760px to 1024px so
   tablet-landscape widths (the ~760–1024px range, where a fixed 240px rail
   left the content column as little as ~660px) also get the top-bar layout;
   above this, laptops/desktops keep the side rail with a comfortable ≥784px
   content column. */

/* ── Agents: "something needs you" ──────────────────────────────────────
   A count, not a dot: "three things broke" and "one thing broke" are
   different mornings, and a dot makes them look the same. Server-rendered
   (_app_rail.html) so it is right in the first paint — a badge whose whole
   job is to catch you on the way past cannot afford to appear a beat late.

   Danger, not warn. A run that failed did not half-work, and the reader is
   being asked to go and look at something already broken. Tokens only, so it
   follows the instance's theme like everything else.

   TOP LEVEL, above the ≤1024px reflow query. An earlier edit inserted this
   region by finding "the last closing brace", which put the base rule inside
   the neighbouring `(hover: none)` query — so the badge had no styling at all
   on an ordinary desktop — and nested the collapsed-strip rules INSIDE this
   rule, where CSS ignores them outright. Both only showed up by looking at a
   collapsed rail. */
html[data-ui-layout="rail"] .rail-i-attn {
    margin-left: auto;
    flex: 0 0 auto;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--ds-radius-pill);
    background: var(--ds-accent-danger-bg);
    color: var(--ds-accent-danger-ink);
    font-size: 11px;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

html[data-ui-layout="rail"] .rail-i-attn.is-quiet {
    background: var(--ds-surface-sunken);
    color: var(--ds-text-secondary);
}

html[data-ui-layout="rail"] .rail-i-dot {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    margin-left: 6px;
    border-radius: var(--ds-radius-pill);
    background: var(--ds-primary);
}

/* Collapsed to the 56px glyph strip the label is hidden and this is NOT — it
   is the only thing left that can carry the signal, so it moves onto the
   icon's corner rather than sitting in a row with no room for it.
   `margin-left: auto` would push it off the strip, hence the reset.

   Desktop-only, like every other `.rail-icon-mode` rule: below 1025px the rail
   is a wrapping top bar and the collapse mechanism must not reach into it
   (tests/test_web_admin_nav.py::TestRailCollapseCss). */
@media (min-width: 1025px) {
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-i { position: relative; }
    html[data-ui-layout="rail"] .rail.rail-icon-mode .rail-i-attn {
        position: absolute;
        top: 3px;
        left: 24px;
        margin-left: 0;
        min-width: 16px;
        height: 16px;
        padding: 0 4px;
        font-size: 10px;
        line-height: 16px;
        /* A ring in the rail's own surface so the badge reads as ON the glyph
           rather than beside it, whatever the icon underneath is doing. */
        box-shadow: 0 0 0 2px var(--ds-surface);
    }
}

/* ── Third rail presentation: the horizontal topbar ──────────────────────
 * `.rail-topbar` (stamped on the SAME `<nav class="rail">` this whole sheet
 * already styles — see the class-assembly comment in `_app_rail.html`) lays
 * every zone out in a 56px bar across the TOP of the page instead of a
 * 240px (or icon-mode-collapsed) column down the left edge. No markup fork:
 * every rule below targets a zone that already exists in the column layout,
 * reflowed by CSS alone — the same precedent `.rail-icon-mode` set earlier
 * in this file.
 *
 * Gated to `min-width: 1025px` on purpose: below it the existing
 * narrow-width query further down already turns the rail
 * into its own wrapping bar, unconditionally — that behaviour must not
 * change just because an instance also chose the topbar presentation for
 * wide viewports (tests/test_rail_topbar_mode.py::TestTopbarGatedToDesktop
 * pins both directions: none of this leaks into that block, and none of it
 * sits unscoped outside a `min-width: 1025px` query either).
 *
 * Every zone the rail can render has an explicit rule in here — positioning
 * or hiding, either counts — checked by
 * tests/test_rail_topbar_mode.py::TestTopbarZoneCoverage, which derives the
 * zone list from _app_rail.html's own structure rather than a
 * hand-maintained one a new zone could silently fall out of. The reason
 * that matters, found the hard way in a throwaway prototype: the rail
 * carries plain `<script>` tags between its real zones (the icon-mode
 * bootstrap, the setup-chain skip check), and an unscoped `.rail.rail-topbar
 * > *` rule turns each into a flex item and prints its JS source as visible
 * text in the bar.
 *
 * Two things this mode does that the prototype it started from did not:
 *
 *   1. The conversation history (`.rail-history`) is NOT on screen in this
 *      mode. `#nav-chats` (the column rail's collapsed-only stand-in for the
 *      same region) is revealed and moved into the "…" menu by
 *      js/rail_topbar_overflow.js, where it navigates to /chats — the full
 *      conversation list. A history popover off that row was built first
 *      and then could never run once the row lived inside another popover,
 *      so it is gone rather than kept as dead code.
 *   2. `_app_rail.html` keeps this mode and the admin icon-strip
 *      (`.rail-icon-mode`) mutually exclusive rather than combined — a
 *      vertical column collapsed to a narrower vertical strip has no
 *      coherent combination with a bar that is already horizontal.
 */

/* The overflow trigger ("…") is new chrome the column layout never had at
   all — unlike everything reflowed above, there is no column-mode form of
   it to fold or hide, so the default (every width, every presentation) is
   simply absent. Only the topbar block below turns it on. Kept OUTSIDE the
   `min-width: 1025px` query on purpose, the same way `.rail
   .rail-i--collapsed-only`'s folded default sits outside it too — a rule
   that only ever un-hides something must not also be the rule that hides it
   by default, or the default would vanish along with the query at narrower
   widths. */
html[data-ui-layout="rail"] .rail-overflow {
    display: none;
}

@media (min-width: 1025px) {
    html[data-ui-layout="rail"] body:has(.rail.rail-topbar) {
        padding-left: 0;
        padding-top: 56px;
        --rail-clearance: 0px;
        /* Body padding clears the bar for normal flow only; a `position:
           sticky` element pins against the VIEWPORT and slid under the bar
           on scroll (Devin Review on #2524). Every page-level sticky reads
           this, so the bar's height is stated once, here. */
        --app-header-height: 56px;
    }

    html[data-ui-layout="rail"] .rail.rail-topbar {
        top: 0;
        left: 0;
        right: 0;
        bottom: auto;
        width: auto;
        height: 56px;
        flex-direction: row;
        align-items: center;
        flex-wrap: nowrap;
        gap: 10px;
        padding: 0 18px;
        border-right: 0;
        border-bottom: 1px solid var(--ds-border);
        /* NOT `overflow: hidden`. The search results, the onboarding panel
           and the profile menu are absolutely positioned descendants that
           open BELOW the bar; a clipping bar removed all three from view
           while every existence test still passed (Devin Review on
           #2524). Horizontal containment comes from `flex-wrap: nowrap`
           and `min-width: 0` on the collapsible, not from clipping. */
        overflow: visible;
    }

    /* See the section header above — these two are the rail's own inline
       <script> tags, neither carrying a class or id, matched by element
       type and scoped to their two actual parents (`<nav>` directly, and
       `.rail-foot`). */
    html[data-ui-layout="rail"] .rail.rail-topbar > script,
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-foot > script {
        display: none;
    }

    /* Normally the ≤1024px bar's one collapsible unit (both nav zones, the
       history and Admin, folded behind `.rail-collapse-toggle`) — here it is
       simply the topbar's own flex row, always expanded. There is nothing
       to collapse in a topbar: no remembered icon-mode width, and
       `.rail-collapse-toggle` is not this bar's control (hidden below). */
    html[data-ui-layout="rail"] .rail.rail-topbar #rail-collapsible {
        display: flex;
        flex-direction: row;
        align-items: center;
        flex-wrap: nowrap;
        gap: 6px;
        flex: 1 1 auto;
        min-width: 0;
        height: auto;
        overflow: visible;
    }

    /* The two controls inside placed zones that still need a placement of
       their own. Inheriting one from the parent zone is what let
       `.rail-toggle` ship as a live, do-nothing control right next to the
       logo — so the sweep demands an explicit rule per control, and these
       state what the bar actually needs rather than restating a default:
       the wordmark never shrinks as items are added. */
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-i-kbd {
        display: none;
    }
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-logo {
        flex: 0 0 auto;
    }

    html[data-ui-layout="rail"] .rail.rail-topbar .rail-logo-row {
        order: 0;
        flex: 0 0 auto;
        margin: 0;
        padding: 0;
    }
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-nav-top {
        order: 1;
    }
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-nav-browse {
        order: 2;
        /* `.rail-flat` draws a divider above this zone in the column; a bar
           has no "above". */
        border-top: 0;
        margin-top: 0;
    }
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-divider {
        display: none;
    }
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-admin {
        order: 3;
        margin-top: 0;
        margin: 0;
        padding: 0;
        border: 0;
    }
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-nav {
        display: flex;
        flex-direction: row;
        align-items: center;
        gap: 2px;
        margin: 0;
        padding: 0;
    }
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-i {
        white-space: nowrap;
        width: auto;
        margin: 0;
    }

    /* `#nav-chats` ("Chats") is folded away unconditionally outside
       icon-mode (the rule near the top of this file, `.rail
       .rail-i--collapsed-only`) — the column rail's own lists are the
       better answer there. The topbar has no inline room for those lists
       either, so the row is the answer here: revealed, and moved into the
       "…" menu by js/rail_topbar_overflow.js, where it is a plain link to
       /chats. */
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-i--collapsed-only {
        height: auto;
        min-height: 0;
        margin: 0;
        padding-top: 7px;
        padding-bottom: 7px;
        opacity: 1;
        visibility: visible;
        overflow: visible;
        transition: none;
    }

    /* Zones with no useful form at 56px tall — exactly the classes
       `.rail-icon-mode` already hides at the narrower column width, reused
       rather than reinvented. */
    /* `.rail-toggle` collapses the COLUMN rail to a strip; a 56px bar has
       nothing to collapse, so the control was live and did nothing. It sits
       nested inside `.rail-logo-row`, which is why the zone sweep — direct
       children only — did not catch it; that sweep now descends. */
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-toggle,
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-collapse-toggle,
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-user-txt {
        display: none;
    }
    /* NOT `.rail-setupchain`. The admin setup chain shares the onboarding
       launcher's anatomy — a `.rail-i` toggle with the progress ring, the
       step list inside the same `.rail-getstarted-panel` popover — so it
       renders here exactly as the analyst ring does: ring in the bar, title
       and sub hidden by the rule above, panel opening below. Hiding the
       whole card removed an admin's setup progress from the bar with no
       other surface carrying it (Devin Review on #2524). */

    html[data-ui-layout="rail"] .rail.rail-topbar .rail-foot {
        order: 10;
        flex: 0 0 auto;
        width: auto;
        margin: 0;
        padding: 0;
        border: 0;
        display: flex;
        flex-direction: row;
        align-items: center;
        gap: 8px;
    }
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-getstarted {
        order: 8;
        flex: 0 0 auto;
        margin: 0;
        padding: 0;
    }
    /* The onboarding ring's panel normally opens UPWARD (anchored to the
       foot of a column) — in a 56px-tall bar it has to open downward
       instead, the same fix the ≤1024px block already makes for the same
       geometry reason. */
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-getstarted-panel {
        bottom: auto;
        top: calc(100% + 8px);
        /* The base rule stretches the panel across its container (`left: 0;
           right: 0`) — the whole 240px column there, but in the bar the
           container is the launcher itself, ~60px, and six setup steps
           wrapped syllable by syllable in a sliver (Devin Review on #2524,
           found while unhiding the admin chain). Its own width, hung off
           the launcher's right edge — the launcher sits at the right end of
           the bar, so the panel grows inward, never off-screen. */
        left: auto;
        right: 0;
        width: 288px;
    }

    /* The profile row — last in the bar. Its dropdown has the identical
       upward-anchored-by-default problem the onboarding panel does, fixed
       the same way. */
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-user-menu {
        order: 11;
        flex: 0 0 auto;
        margin: 0;
        padding: 0;
        border: 0;
    }
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-user-menu .app-user-menu-panel {
        bottom: auto;
        top: calc(100% + 8px);
        left: auto;
        right: 0;
    }

    /* The Help row — ahead of the onboarding slot (`.rail-setup` at the
       flex default, `.rail-getstarted` at 8) and the profile (11), so the
       bar reads Help · your setup · you, left to right, in the order the
       column reads top to bottom. Its zone rule (border-top, the extra top
       padding) marks off the foot's last group in a column; in a row of
       icons it would draw a line above one item, so both go. */
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-account {
        display: contents;
    }
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-account > .rail-user-menu {
        position: relative;
    }
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-user-chev {
        display: none;
    }
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-account > .rail-bell,
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-help {
        order: -1;
        flex: 0 0 auto;
        margin: 0;
        padding: 0;
        border: 0;
    }

    /* The `›` chevron promises a menu opening SIDEWAYS from a column row;
       in the bar the menu opens downward, so the promise would be wrong. */
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-help-chev {
        display: none;
    }

    /* Same geometry fix as the onboarding panel and the profile menu right
       above: the column's menu hangs off the row's right edge and grows
       upward; a 56px bar has no room either way, so it opens below and,
       like the overflow panel, grows inward from its own right edge. */
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-help-panel {
        bottom: auto;
        top: calc(100% + 8px);
        left: auto;
        right: 0;
    }

    /* The conversation history has no form at 56px and no popover of its
       own any more (see item 1 of the section header): hidden outright.
       Its markup stays in the DOM so the column rail's rail_history.js —
       empty and failed states included — is untouched by this mode. */
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-history {
        display: none;
    }

    /* The overflow trigger — see `#rail-overflow` in _app_rail.html. An
       ORDINARY `.rail-i` (icon, hover, active tint — `--rail-active-bg` via
       `.on`, set server-side from the same checks `#nav-chats` and
       `#nav-agents` use), positioned after Admin and before the search box
       so the row it stands in for reads left-to-right the same way the main
       row always has. */
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-overflow {
        display: flex;
        order: 4;
        flex: 0 0 auto;
        margin: 0;
        padding: 0;
    }

    /* Chats and Agents leave the main row entirely in this mode — they
       reappear, floated, only while the overflow popover is open (below). */
    html[data-ui-layout="rail"] .rail.rail-topbar #nav-chats,
    html[data-ui-layout="rail"] .rail.rail-topbar #nav-agents {
        display: none;
    }

    /* The menu is ONE panel under the trigger. The rows inside are the rail's
       own `.rail-i` elements, moved here by js/rail_topbar_overflow.js, so
       they keep the row shape the bar already uses and only the box around
       them is new.

       `position: fixed`, and js/rail_topbar_overflow.js sets top/right from
       the trigger's rectangle. The bar no longer clips (see its own rule),
       so `absolute` would work too; fixed is kept because the panel's
       anchor is the trigger, whose x shifts with which rows are gated in,
       and one measured corner is simpler than a positioned wrapper. */
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-overflow-panel {
        display: none;
        position: fixed;
        /* Pre-JS fallback only; the module overwrites both on every open. */
        top: 62px;
        right: 18px;
        min-width: 216px;
        padding: 6px;
        z-index: 60;
        flex-direction: column;
        gap: 2px;
        background: var(--ds-surface);
        border: 1px solid var(--ds-border);
        border-radius: 10px;
        box-shadow: var(--ds-shadow-lg);
    }
    html[data-ui-layout="rail"] .rail.rail-topbar #rail-overflow.is-open .rail-overflow-panel {
        display: flex;
    }
    /* Beats the `#nav-…` hide rules above — those keep the rows out of the
       MAIN row, including the frame before the script moves them. Inside the
       panel they are visible again. */
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-overflow-panel #nav-chats,
    html[data-ui-layout="rail"] .rail.rail-topbar .rail-overflow-panel #nav-agents {
        display: flex;
        width: 100%;
    }
}

@media (max-width: 1024px) {
    /* Selector kept in lockstep with the desktop rule above: `:has()` takes the
       specificity of its argument, so a plain `body` here would now LOSE to
       `body:has(.rail)` and the 240px reservation would survive into the
       top-bar layout. Same specificity, later in the file → this wins. */
    html[data-ui-layout="rail"] body:has(.rail) {
        padding-left: 0;
        /* The rail is a top bar here, so there is no left edge to clear — and
           the fixed chrome keyed on this had its own 240px hardcoded with a
           `max-width: 760px` escape hatch, leaving the docked toolbar inset
           240px from nothing across the whole 761–1024px band. */
        --rail-clearance: 0px;
        --app-header-height: var(--rail-bar-bottom, 0px);
    }

    html[data-ui-layout="rail"] .rail {
        position: static;
        width: auto;
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        border-right: none;
        border-bottom: 1px solid var(--ds-border);
        /* The base rule sets `overflow: visible` so the Studio hover flyout can
           escape the fixed column's right edge — harmless there since the rail
           IS the viewport-height column. In this row layout `.rail` is back in
           normal document flow, so unconstrained overflow drags the whole page
           horizontally (most visibly when the tour scrolls a nav item into
           view). Cap the bar's own width — but keep `.rail` itself
           `overflow: visible`: making the BAR the scroll box (`overflow-x:
           auto`) axis-couples `overflow-y` to `auto` per the CSS Overflow
           spec, clipping the Get-started popover and the account menu that
           open downward out of the bar (Devin Review on #1092). The scroll
           containment lives on `.rail-nav` below instead — the popover and
           user-menu panels are its SIBLINGS, so they escape freely while the
           nav list (the thing the tour scrolls into view) scrolls
           internally. */
        max-width: 100%;
    }

    html[data-ui-layout="rail"] .rail-nav {
        flex-direction: row;
        flex-wrap: wrap;
        /* See the `.rail` comment above: this is the scroll box that keeps a
           tour-driven scrollIntoView from dragging the page sideways, scoped
           to an element that contains no popovers. */
        max-width: 100%;
        overflow-x: auto;
    }

    /* `.rail-i` is `width: 100%` for the column layout, where every item
       should span the rail's fixed 240px. Left unscoped here, that same
       100% resolves against the row's own width inside a wrapping flex
       container — each item claims a full line, which is the other half of
       why the bar overflowed (independent of the overflow fix above). */
    html[data-ui-layout="rail"] .rail-i {
        width: auto;
    }

    /* New chat needs no override of its own — it is a `.rail-i`, so the rule
       above already covers it. */

    /* The 8px that separates New chat from the recents is a COLUMN metric.
       In the bar the two are laid out side by side, where it reads as a stray
       right-hand gap. */
    html[data-ui-layout="rail"] .rail-nav-top {
        margin-bottom: 0;
    }

    /* The column's 14px under the wordmark is air above the first section
       label. Here the logo shares a row with the collapse toggle, where the
       same value reads as the mark sitting high in its own row. */
    html[data-ui-layout="rail"] .rail-logo-row {
        padding-bottom: 8px;
        /* The band the toggle centres on moves with the padding — see the
           note on these two at the top of this file. The toggle is hidden at
           this breakpoint today, so nothing reads them here yet; they are set
           anyway so the invariant holds by construction rather than by that
           happening to stay true. */
        --rail-logo-row-pt: 6px;
        --rail-logo-row-pb: 8px;
    }

    /* The collapsed/expanded rail-width preference has no meaning below
       1025px — the ≤1024px query above already turns the rail into a
       wrapping top bar with its own (unrelated) collapse toggle
       (`.rail-collapse-toggle`) — so the width control has nothing to do
       here and would just be a second, confusing button beside it. */
    html[data-ui-layout="rail"] .rail-toggle {
        display: none;
    }

    /* The conversations region is sized by the COLUMN layout's free space:
       `.rail-history: flex: 1 1 auto` over a body with `flex-basis: 0`, which
       resolves to a real height only inside the rail's fixed viewport height.
       Here `.rail` is an auto-height bar, so there is no free space to grow
       into and the whole region collapsed to 0 — Pinned and Chats were rendered
       and then clipped to nothing, i.e. the expanded bar showed every nav row
       and no conversations at all (which the collapse toggle's own contract
       says it should).

       Content-driven height instead, capped so a long history can't push the
       nav rows and the profile off a phone screen: the lists still scroll
       inside the same box, with the same fade. */
    html[data-ui-layout="rail"] .rail-history {
        flex: 0 0 auto;
    }

    html[data-ui-layout="rail"] .rail-history-body {
        flex: 0 1 auto;
        max-height: 45vh;
    }

    html[data-ui-layout="rail"] .rail-collapse-toggle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 40px;
        height: 40px;
        margin-left: auto;
        border: 1px solid var(--ds-border);
        border-radius: 9px;
        background: var(--ds-surface);
        color: var(--ds-text-secondary);
        cursor: pointer;
        flex-shrink: 0;
    }

    html[data-ui-layout="rail"] .rail-collapse-toggle svg {
        width: 20px;
        height: 20px;
    }

    html[data-ui-layout="rail"] .rail-collapsible {
        display: none;
        /* Always its own full-width line below the logo+toggle row, rather
           than squeezing in beside them (.rail is flex-wrap: wrap here). */
        flex: 1 1 100%;
        flex-direction: column;
    }

    html[data-ui-layout="rail"] .rail.is-nav-open .rail-collapsible {
        display: flex;
    }

    html[data-ui-layout="rail"] .rail-collapsible,
    html[data-ui-layout="rail"] .rail-history,
    html[data-ui-layout="rail"] .rail-history-panel,
    html[data-ui-layout="rail"] .rail-history-body {
        min-width: 0;
        max-width: 100%;
    }
    html[data-ui-layout="rail"] .rail-foot .rail-i {
        width: 100%;
    }
    html[data-ui-layout="rail"] .rail .rail-i-kbd {
        display: none;
    }

    html[data-ui-layout="rail"] .rail-nav.rail-admin-nav {
        flex-direction: column;
        flex-wrap: nowrap;
        overflow: visible;
    }

    html[data-ui-layout="rail"] .rail:not(.is-nav-open) .rail-collapsible {
        display: contents;
    }
    html[data-ui-layout="rail"] .rail:not(.is-nav-open) .rail-collapsible > :not(.rail-nav-top),
    html[data-ui-layout="rail"] .rail:not(.is-nav-open) .rail-foot,
    html[data-ui-layout="rail"] .rail:not(.is-nav-open) .rail-nav-top > :not(#new-chat, #rail-search-trigger, #rail-back) {
        display: none;
    }
    html[data-ui-layout="rail"] .rail:not(.is-nav-open) .rail-nav-top {
        order: 1;
        flex: 0 0 auto;
        flex-wrap: nowrap;
        gap: 4px;
        margin-left: auto;
        overflow: visible;
    }
    html[data-ui-layout="rail"] .rail:not(.is-nav-open) .rail-nav-top > .rail-i {
        justify-content: center;
        width: 40px;
        height: 40px;
        margin: 0;
        padding: 0;
    }
    html[data-ui-layout="rail"] .rail:not(.is-nav-open) .rail-nav-top .rail-i-label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
    html[data-ui-layout="rail"] .rail:not(.is-nav-open) .rail-nav-top .rail-i-kbd {
        display: none;
    }
    html[data-ui-layout="rail"] .rail:not(.is-nav-open) .rail-collapse-toggle {
        order: 2;
        margin-left: 4px;
    }

    html[data-ui-layout="rail"] .rail-foot {
        margin: 0;
        border: none;
        padding: 0;
        flex-direction: column;
        align-items: stretch;
        gap: var(--rail-nav-gap, 3px);
        flex: 1 1 100%;
        min-width: 0;
    }

    html[data-ui-layout="rail"] .rail-foot > *,
    html[data-ui-layout="rail"] .rail-foot .rail-i,
    html[data-ui-layout="rail"] .rail-foot .rail-user {
        min-width: 0;
        max-width: 100%;
    }

    /* The help/account divider is a bottom-of-column separator; in the row
       layout it would read as a stray top border, so drop it. (`.rail-user-menu`
       is listed too, for the same reason it always was — it no longer draws one,
       but a stale rule is cheaper to keep honest than to rediscover.) */
    html[data-ui-layout="rail"] .rail-help,
    html[data-ui-layout="rail"] .rail-user-menu {
        border-top: none;
        padding-top: 0;
        margin-top: 0;
    }

    /* The rail is at the TOP of the page here, so both menus open DOWNWARD —
       and both anchor to their own RIGHT edge, opening leftward.

       The column versions are `left: 0`, which is right in a 240px column and
       wrong in this bar: the foot becomes a ROW and the `?` and the account sit
       at its right end, so a 240px panel hung off their left edge ran straight
       off the side of the viewport with half its labels unreachable. The
       onboarding popover already carries this exact rule for this exact reason
       (see the note on `.rail-getstarted-panel` below); these two were simply
       never given it — the account menu because its column rule predates the
       bar, the `?` because it is new. */
    /* The account menu hangs off its row's RIGHT edge: the account is the
       last thing in the foot's row, so growing leftward keeps it on screen. */
    html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-panel {
        bottom: auto;
        top: calc(100% + 8px);
        left: auto;
        right: 0;
    }
    /* Help hangs off its row's LEFT edge. In this bar the foot's rows sit at
       the start of their line — Help first, at x≈8 — so a panel anchored to
       the row's right edge stood 130px off the left of the viewport (found by
       walking the 1000px layout: the menu opened and nothing was there). */
    html[data-ui-layout="rail"] .rail-help-panel {
        bottom: auto;
        top: calc(100% + 8px);
        left: 0;
        right: auto;
        max-width: calc(100vw - 16px);
    }

    /* The onboarding card rides inside .rail-collapsible now (it closes the
       bottom nav rather than sitting in the foot), so it appears with the rest
       of the nav when the bar is expanded and needs no compact form of its
       own — the 40×40 icon-with-badge treatment it used to get in the foot row
       is gone with the row. Only the popover has to move: it opens DOWNWARD
       here, since the rail is at the top of the page. */


    /* Popover opens downward too when the rail is a top bar, and anchors to the
       launcher's RIGHT edge (opening leftward) since the compact icon button
       sits at the right end of the bar — a left-anchored 280px panel would run
       off the viewport. */
    /* Left-anchored for the same reason as the Help menu above: the setup
       and Getting started rows sit near the start of the foot's line, and a
       right-anchored 280px panel fell off the viewport's left edge. */
    html[data-ui-layout="rail"] .rail-getstarted-panel {
        bottom: auto;
        top: calc(100% + 8px);
        left: 0;
        right: auto;
        width: 280px;
        max-width: calc(100vw - 16px);
    }
}

@media (max-width: 600px) {
    html[data-ui-layout="rail"] .rail.rail-admin-mode:not(.is-nav-open) .rail-nav-top > #rail-back {
        width: auto;
        gap: 8px;
        padding: 0 12px 0 10px;
    }
    html[data-ui-layout="rail"] .rail.rail-admin-mode:not(.is-nav-open) #rail-back .rail-i-label {
        position: static;
        width: auto;
        height: auto;
        overflow: visible;
        clip-path: none;
    }
    html[data-ui-layout="rail"] .rail:not(.is-nav-open) .rail-logo-sub {
        display: none;
    }
}

@media (min-width: 601px) and (max-width: 1024px) {
    html[data-ui-layout="rail"] .rail:not(.is-nav-open) .rail-nav-top > .rail-i {
        width: auto;
        gap: 8px;
        padding: 0 12px 0 10px;
    }
    html[data-ui-layout="rail"] .rail:not(.is-nav-open) .rail-nav-top .rail-i-label {
        position: static;
        width: auto;
        height: auto;
        overflow: visible;
        clip-path: none;
    }
}

/* ── Phones: the foot's panels become bottom sheets ─────────────────────
   Below ~600px the bar wraps onto two lines and its rows land wherever the
   wrap puts them, so a panel hung off a row's edge — left or right — runs
   off the viewport on one side or the other (measured at 375px: the account
   menu at x=-54, the setup panel 20px past the right edge). Pinning the
   sheet to the viewport instead of the row is the one geometry that fits
   every wrap. Fixed, full width less a gutter, sitting on the bottom edge;
   tall content scrolls inside. */
@media (max-width: 600px) {
    html[data-ui-layout="rail"] .rail-help-panel,
    html[data-ui-layout="rail"] .rail-getstarted-panel,
    html[data-ui-layout="rail"] .rail-user-menu .app-user-menu-panel {
        position: fixed;
        top: auto;
        bottom: 8px;
        left: 8px;
        right: 8px;
        width: auto;
        max-width: none;
        max-height: 70vh;
        overflow-y: auto;
    }
}




@keyframes rail-drawer-in {
    from { transform: translateX(-16px); opacity: 0; }
    to { transform: none; opacity: 1; }
}

html[data-ui-layout="rail"] .rail-collapse-toggle-close {
    display: none;
}

@media (max-width: 1024px) {
    html[data-ui-layout="rail"] .rail-shortcuts-row--wide {
        display: none;
    }
    html[data-ui-layout="rail"] .rail-collapse-toggle[aria-expanded="true"] .rail-collapse-toggle-open {
        display: none;
    }
    html[data-ui-layout="rail"] .rail-collapse-toggle[aria-expanded="true"] .rail-collapse-toggle-close {
        display: block;
    }
    html[data-ui-layout="rail"] body:has(.rail.is-nav-open) {
        padding-top: var(--rail-bar-h, 0px);
        overflow: hidden;
    }
    html[data-ui-layout="rail"] body:has(.rail.is-nav-open)::before {
        content: "";
        position: fixed;
        inset: 0;
        z-index: 59;
        background: color-mix(in srgb, black 32%, transparent);
    }
    html[data-ui-layout="rail"] .rail.is-nav-open {
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        width: min(320px, calc(100vw - 48px));
        flex-direction: column;
        flex-wrap: nowrap;
        align-items: stretch;
        border-bottom: none;
        border-right: 1px solid var(--ds-border);
        box-shadow: var(--ds-shadow-lg);
        overflow: hidden;
        z-index: var(--rail-z-popover);
        --rail-row-h: 34px;
        animation: rail-drawer-in var(--ds-motion-fast, .16s) var(--ds-ease-standard, ease);
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-logo-row {
        padding-right: 96px;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open #rail-search-trigger {
        display: none;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-search-icon {
        display: inline-flex;
        position: absolute;
        top: 12px;
        right: 60px;
        width: 40px;
        height: 40px;
        border: 1px solid var(--ds-border);
        border-radius: 9px;
        background: var(--ds-surface);
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-account > .rail-help {
        flex: 0 0 36px;
        width: 36px;
    }

    html[data-ui-layout="rail"] .rail.is-nav-open .rail-help-trigger {
        max-width: none;
        width: 36px;
        height: 36px;
        min-height: 36px;
        padding: 0;
        justify-content: center;
        border-radius: var(--ds-radius-pill, 999px);
    }

    html[data-ui-layout="rail"] .rail.is-nav-open .rail-help-trigger svg {
        width: 17px;
        height: 17px;
    }

    html[data-ui-layout="rail"] .rail.is-nav-open .rail-help-trigger .rail-i-label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-account > .rail-help {
        order: 2;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open :is(.rail-help-item, .rail-user-menu .app-user-menu-item) {
        min-height: 40px;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open :is(.rail-chatsec-toggle, .rail-history-all) {
        min-height: 36px;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-nav-top {
        margin-bottom: calc(var(--rail-nav-gap, 1px) - 2px);
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-admin {
        position: sticky;
        bottom: 0;
        z-index: 1;
        margin-top: auto;
        padding-top: 4px;
        background: var(--ds-surface);
    }
    html[data-ui-layout="rail"] .rail:not(.is-nav-open) {
        position: sticky;
        top: 0;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-account > .rail-bell {
        order: 1;
        position: relative;
        flex: 0 0 36px;
        width: 36px;
        max-width: none;
        height: 36px;
        min-height: 36px;
        padding: 0;
        justify-content: center;
        border-radius: var(--ds-radius-pill, 999px);
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-bell .rail-i-label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-bell .rail-i-attn {
        position: absolute;
        top: 0;
        right: -2px;
        margin: 0;
        min-width: 16px;
        height: 16px;
        padding: 0 4px;
        font-size: 10px;
        line-height: 16px;
        box-shadow: 0 0 0 2px var(--ds-surface);
    }
    html[data-ui-layout="rail"] .rail-collapse-toggle.has-unread {
        position: relative;
    }
    html[data-ui-layout="rail"] .rail-collapse-toggle.has-unread::after {
        content: "";
        position: absolute;
        top: 7px;
        right: 7px;
        width: 8px;
        height: 8px;
        border-radius: var(--ds-radius-pill, 999px);
        background: var(--ds-accent-danger-ink);
        box-shadow: 0 0 0 2px var(--ds-surface);
    }
    html[data-ui-layout="rail"] .rail-collapse-toggle.has-unread.is-quiet::after {
        background: var(--ds-primary);
    }
    html[data-ui-layout="rail"] .rail-collapse-toggle.has-unread[aria-expanded="true"]::after {
        display: none;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-search-icon svg {
        width: 20px;
        height: 20px;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-collapse-toggle {
        position: absolute;
        top: 12px;
        right: 12px;
        margin: 0;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-collapsible {
        flex: 1 1 auto;
        min-height: 0;
        overflow-x: hidden;
        overflow-y: auto;
        overscroll-behavior: contain;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-nav {
        flex-direction: column;
        flex-wrap: nowrap;
        overflow: visible;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-i {
        width: 100%;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-history-body {
        max-height: none;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open .rail-foot {
        flex: 0 0 auto;
        margin-top: 8px;
    }
    html[data-ui-layout="rail"] .rail.is-nav-open :is(.rail-help-panel, .rail-getstarted-panel, .rail-user-menu .app-user-menu-panel) {
        position: fixed;
        top: auto;
        bottom: 60px;
        left: 8px;
        right: auto;
        width: calc(min(320px, 100vw - 48px) - 16px);
        max-width: none;
        max-height: 70vh;
        overflow-y: auto;
    }
}

@media (max-width: 1024px) and (prefers-reduced-motion: reduce) {
    html[data-ui-layout="rail"] .rail.is-nav-open {
        animation: none;
    }
}

/* ── The admin setup chain, in the get-started slot ──────────────────────
   The row reuses `.rail-getstarted` wholesale — launcher, ring, hover /
   .is-open panel behaviour, icon-mode label hiding — so only the step list
   inside the panel is new. Reusing the class rather than copying its rules
   is what keeps the two rows the same height and the bottom zone from
   shifting when one replaces the other. */
/* The chevron's row. Nothing but the button, pushed right — see the note in
   `_app_rail.html` for why there is no title beside it. The negative top
   margin pulls it back into the panel's own padding so the chevron sits IN the
   corner rather than on a reserved band of its own, and `margin-bottom` is
   negative for the same reason: the row must not push the first step down, or
   adding an affordance would cost the list a line of height. */
/* ── The analyst card, dismissed ──────────────────────────────────────────
   "I do not want to see this" is a display preference, not a claim about what
   the caller has done, so it is a per-browser flag rather than a write to the
   journey record. `js/chat_onboarding.js` owns the write; the inline script in
   `_app_rail.html` stamps it before first paint so a dismissed card never
   flashes. Failing toward SHOWING the card if storage throws: a checklist that
   reappears is a smaller harm than progress that was never made.

   `:not(.rail-setupchain)`: the admin's Finish setup card shares the wrapper
   class for its geometry, and it has its own flag and its own rule directly
   below — an admin who skipped the analyst checklist in this browser must not
   lose the setup card with it.

   (This rule went missing for one build, cut along with the setup card's own
   dismissal rules that sat beside it; tests/test_journey_milestones.py caught
   it on the next merge.) */
html[data-ui-layout="rail"][data-journey-skipped="1"] .rail-getstarted:not(.rail-setupchain) {
    display: none;
}

/* ── The admin setup card, hidden ─────────────────────────────────────────
   Same shape as the rule above, for the other occupant of the slot: a
   per-browser flag (`agnes.setupchain.skipped`), written by
   `js/rail_setupchain.js` and stamped before first paint by the inline script
   beside the card. Both wrapper classes in the selector, so it carries the
   same weight as the rule above and the two can never be told apart by
   specificity — only by which flag is set. */
html[data-ui-layout="rail"][data-setupchain-skipped="1"] .rail-getstarted.rail-setupchain {
    display: none;
}

html[data-ui-layout="rail"] .rail-setupchain-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 4px 9px 8px;
}
html[data-ui-layout="rail"] .rail-setupchain-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--ds-text-primary);
}
html[data-ui-layout="rail"] .rail-setupchain-sub {
    font-size: 12px;
    color: var(--ds-text-muted);
    font-variant-numeric: tabular-nums;
}
html[data-ui-layout="rail"] .rail-setupchain-head .cloud-chat-journey-iconbtn {
    margin-left: auto;
    align-self: center;
}
/* One door to the whole chain on /admin — a quiet footer link, not a button:
   the steps above are the actions, this is where they live. */
html[data-ui-layout="rail"] .rail-setupchain-all {
    display: block;
    margin-top: 4px;
    padding: 8px 9px 4px;
    border-top: 1px solid var(--ds-border);
    font-size: 12px;
    color: var(--ds-text-muted);
    text-decoration: none;
}
html[data-ui-layout="rail"] .rail-setupchain-all:hover,
html[data-ui-layout="rail"] .rail-setupchain-all:focus-visible {
    color: var(--ds-text-primary);
}
html[data-ui-layout="rail"] .rail-setupchain-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}
html[data-ui-layout="rail"] .rail-setupchain-step a {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 9px;
    border-radius: 7px;
    color: var(--ds-text-primary);
    text-decoration: none;
    font-size: 13px;
    line-height: 1.35;
}
html[data-ui-layout="rail"] .rail-setupchain-step a:hover {
    background: var(--ds-surface-sunken);
}
/* The marker is the only thing separating the three states at a glance, so
   it is dual-encoded — glyph AND colour — rather than colour alone. */
html[data-ui-layout="rail"] .rail-setupchain-mark {
    flex: 0 0 auto;
    width: 15px;
    text-align: center;
    font-weight: 700;
    color: var(--ds-text-muted);
}
html[data-ui-layout="rail"] .rail-setupchain-step.is-done .rail-setupchain-mark {
    color: var(--ds-accent-success-ink);
}
html[data-ui-layout="rail"] .rail-setupchain-step.is-failed .rail-setupchain-mark {
    color: var(--ds-warn-ink);
}
/* Done steps recede without disappearing: the chain's shape — how far along
   this instance is — is the reason the panel exists, and a list that hides
   what is finished cannot show it. Deliberately NOT struck through; a step
   that can be revisited ("Add another source") is not cancelled. */
html[data-ui-layout="rail"] .rail-setupchain-step.is-done .rail-setupchain-label {
    color: var(--ds-text-muted);
}
html[data-ui-layout="rail"] .rail-setupchain-note {
    margin-left: auto;
    font-size: 11px;
    color: var(--ds-warn-ink);
    white-space: nowrap;
}


