/* ============================================================================
   opps-rail.css — the rail layout, additive over opps-v2.css.

   Scoped entirely under `body.v2rail` (itself only reachable from `body.v2`
   — see app.js v2RailOn()), loaded after opps-v2.css, and every rule here is
   additive: with the flag off (?rail=0, the default) nothing in this file
   matches and the page is byte-for-byte the v2 row layout. Same reversibility
   contract opps-v2.css itself established over the legacy grid.

   What moves: the market bar, should-we-bid, the search box and every
   filter chip relocate from the stacked top bands into #rail — the element
   the page has always shipped and opps-v2.css hides (`body.v2 .rail{display:
   none}`). #main keeps only the results header and the results.

   What does NOT move, and is not reimplemented here: almost every control's
   BEHAVIOR. .v2-fchip, .v2-pop, .v2-mktab and everything inside them are the
   exact same markup opps-v2.css already styles — this file only overrides
   their LAYOUT (block instead of inline, column instead of row). Should we
   bid is the one exception: it is NOT .v2-answer relayed out here — that
   markup never renders in the rail at all (v2VerdictChip() in app.js builds
   an ordinary .v2-fchip instead; see §4c of the design doc for why). The one
   mechanism this layout drops is v2FoldRow's overflow-into-a-shelf folding
   (.v2-fold/.v2-shelf): that exists to keep a fixed-width horizontal row to
   one line, and a vertical rail has no version of that problem, so app.js's
   v2RailFilters() simply never calls it — every filter is always its own
   visible row here.

   Design + reasoning: docs/opportunity-search-rail-design.md
   ========================================================================== */

/* the rail is back. `body.v2 .rail{display:none}` (opps-v2.css) and this
   rule are equal specificity, so this wins purely on LOAD ORDER — this file
   is linked after opps-v2.css in index.html, and must stay after it, or the
   rail silently goes back to hidden. */
body.v2rail .workspace { display: grid; grid-template-columns: 320px 1fr; min-height: 0; }
body.v2rail .rail {
  display: block; border-right: 1px solid var(--line-soft); overflow-y: auto;
  padding: 1.3rem 1.1rem 3rem; background: var(--bg-panel);
  /* the rail is a full-height scroller, so it renders a scrollbar on every
     viewport shorter than its filter stack — and unstyled that is the PLATFORM
     DEFAULT: a ~15px opaque grey trough sitting between the last filter chip
     and the results column, the loudest element in a panel of quiet ones. Every
     other scroller in this app is thin (opps.css: .pb-board, .pb-dbody, and
     three more); this one was simply missed when the rail layout was added.
     Thumb is --line, NOT the --panel2 those five use: --panel2 is a PANEL fill,
     so on the light theme (appearance.js remaps it to #f3efe6) it lands within
     a few percent of this rail's own #fbf8f1 background and the thumb
     disappears entirely — a scroller with no visible position. --line is an
     ink-alpha in both themes, so it stays quiet on dark and legible on light. */
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}

/* ⚠ `scrollbar-width`/`scrollbar-color` ALONE DO NOT REACH EVERY BROWSER, and the
   rail rule above — plus its twin on the popover's option list, `.v2-pop
   .v2-opts` further down — shipped on 2026-08-26 believing they did. They are the modern
   standard properties — Firefox has honored them for years and Chrome since 121 —
   but SAFARI IGNORED THEM UNTIL 18.2, and WebKit is exactly where the default
   scrollbar is an opaque grey slab rather than a hairline. So the fix verified
   green in headless Chromium and changed NOTHING for a Safari reader, which is how
   it reached production still broken for the person who reported it.
   `::-webkit-scrollbar` is the vendor path those engines do read, and it is already
   the house pattern — web/explorer/platform/account.css styles the same four
   pseudo-elements. Keep BOTH: Chrome lets a styled ::-webkit-scrollbar win over
   scrollbar-width, Firefox reads only the standard properties, so neither is
   redundant and dropping either loses a browser. Widths are deliberately close
   (9px here vs Chrome's ~11px `thin`) so the rail's gutter does not visibly jump
   between engines. */
body.v2rail .rail::-webkit-scrollbar,
body.v2rail .v2-pop .v2-opts::-webkit-scrollbar { width: 9px; height: 9px; }
body.v2rail .rail::-webkit-scrollbar-track,
body.v2rail .v2-pop .v2-opts::-webkit-scrollbar-track { background: transparent; }
body.v2rail .rail::-webkit-scrollbar-thumb,
body.v2rail .v2-pop .v2-opts::-webkit-scrollbar-thumb { background: var(--line); border-radius: 100px; }
body.v2rail .rail::-webkit-scrollbar-thumb:hover,
body.v2rail .v2-pop .v2-opts::-webkit-scrollbar-thumb:hover { background: var(--ink-faint); }
/* No width cap: the results page (table, cards, board) and the pipeline board fill
   the screen beside the rail. A 1400px cap left a quarter of a 1900px monitor empty
   (Kevin, 2026-10-30). Line length is held per column instead — the table's title
   column takes the spare width, cards add a column, board columns widen. */
body.v2rail .main { padding: 18px 26px 90px; max-width: none; margin: 0; }

/* ── the rail's top bar: collapse · "Filters" · Clear all ─────────────────
   #railtog is the first thing railFilterMarkup() renders, in BOTH #rail's uses
   (browse and the pipeline board), inside .v2r-top. The button is a panel icon
   with a chevron that points the way the rail will move, 32px square so it is
   an easy target. Collapsed, the column narrows to a 48px strip holding only
   the button (and a dot when filters are set, so a hidden filter is never
   invisible); every filter's own DOM and state stay as they were, only hidden.
   Clear all sits in the bar, at the top of the rail, as an outlined button —
   it used to be a small link at the foot of the chip list. */
.v2r-top {
  display: flex; align-items: center; gap: 10px;
  margin: -4px 0 14px; padding-bottom: 12px; border-bottom: 1px solid var(--line-soft);
}
.v2r-ttl {
  font-family: var(--mono); font-size: .66rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink-dim);
}
.v2r-clrslot { margin-left: auto; display: flex; }
.railtog {
  position: relative; flex: none; width: 32px; height: 32px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 9px; background: var(--panel);
  color: var(--ink-dim); cursor: pointer; transition: color .15s, border-color .15s, background .15s;
}
.railtog svg { display: block; }
.railtog:hover { color: var(--accent-text); border-color: rgba(232,161,60,.55); background: rgba(232,161,60,.08); }
.railtog:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* "filters are set" — shown on the collapsed strip only */
.railtog-n { display: none; position: absolute; top: -3px; right: -3px; width: 9px; height: 9px;
  border-radius: 50%; background: var(--accent); border: 2px solid var(--bg-panel, var(--panel)); }
body.v2rail.railcollapsed .railtog-n.on { display: block; }
body.v2rail .v2-clrall.v2r-clrall {
  align-self: auto; margin: 0; padding: 6px 13px; border-radius: 999px;
  border: 1px solid rgba(232,161,60,.6); background: rgba(232,161,60,.1);
  color: var(--accent-text); font-family: var(--sans, inherit); font-size: .78rem; font-weight: 500;
  letter-spacing: normal; text-transform: none; cursor: pointer; transition: .15s;
}
body.v2rail .v2-clrall.v2r-clrall:hover { background: rgba(232,161,60,.2); color: var(--ink); }
body.v2rail .v2-clrall.v2r-clrall:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
body.v2rail.railcollapsed .workspace { grid-template-columns: 48px 1fr; }
body.v2rail.railcollapsed .rail { padding: 14px 7px; overflow: hidden; }
body.v2rail.railcollapsed .rail > *:not(.v2r-top) { display: none; }
body.v2rail.railcollapsed .v2r-top { margin: 0; padding: 0; border: 0; justify-content: center; }
body.v2rail.railcollapsed .v2r-top > *:not(.railtog) { display: none; }

/* ── rail sections ─────────────────────────────────────────────────────── */
.v2r-sec { padding-bottom: 14px; margin-bottom: 14px; border-bottom: 1px solid var(--line-soft); }
.v2r-sec:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }
/* ⚠ "Your fields" is the ONLY child of its own wrapper (#v2-cf-rail, filled
   after first paint), so the :last-child rule above read it as the rail's
   last section and stripped its bottom border, padding and margin — SEARCH
   sat flush against the last field chip with no gap (reported 2026-09-02,
   "ensure there is space above SEARCH"). The wrapper is never the last thing
   in the rail; give the section back what every other section has. */
#v2-cf-rail > .v2r-sec:last-child { border-bottom: 1px solid var(--line-soft); margin-bottom: 14px; padding-bottom: 14px; }
/* keyword fields sit under the filters, inside their section (#v2-kw-rail) */
.v2r-kwsec { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line-soft); }
.v2r-lbl {
  font-family: var(--mono); font-size: .64rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--accent-text); margin-bottom: 8px; display: flex; align-items: baseline; gap: .5em;
}
/* "from your lens" — normal weight/case, so it reads as a caption on the
   label rather than shouting the same as the label itself. */
.v2r-sub {
  font-family: var(--sans, inherit); letter-spacing: normal; text-transform: none;
  font-weight: 400; color: var(--ink-faint);
}

/* ── market — A SINGLE-SELECT MENU, the one control here that is NOT the row
   layout's markup relaid out (v2MarketPickHtml, not v2MarketBarHtml).
   Scope is one choice out of a list, and the tab strip only works across a
   900px band because it folds its tail into a shelf. This column has no shelf,
   so the strip became one full-width button per market — seventeen of them
   above the fold on a phone, with Should we bid and every filter pushed off
   screen. .v2-mkbar keeps its column layout for the "no saved markets yet"
   note that can sit under the menu. */
body.v2rail .v2-mkbar { flex-direction: column; align-items: stretch; margin: 0; padding-bottom: 0; border-bottom: none; }
body.v2rail .v2-mkl { margin: 2px 0 0; }
body.v2rail .v2-mkpick { display: flex; align-items: center; gap: 6px; }
/* .v2-mkselwrap takes the width; min-width:0 is what lets a long market name
   shrink inside the flex row instead of pushing the edit button off the
   rail — moved here from the select itself (below) so the wrapper, not the
   native control, is what flexbox is shrinking.

   ⚠ THE SELECT USED TO DRAW ITS OWN NATIVE CARET, AND IT COULD GET CLIPPED —
   reported live on "ANSER NAICS/PSC". A native <select>'s reserved arrow
   width is not a real CSS box a flex row can be sized against; once this
   row's own width narrowed the arrow had nowhere guaranteed to sit and could
   render past the rail's own edge. appearance:none drops that native
   rendering entirely and .v2-mkcaret below draws one at a fixed inset from
   .v2-mkselwrap's edge instead — which cannot run out of room regardless of
   how narrow the label makes the row, because it no longer depends on the
   browser's own idea of how much space its arrow needs. */
body.v2rail .v2-mkselwrap { position: relative; flex: 1; min-width: 0; display: flex; }
body.v2rail .v2-mkpick select {
  flex: 1; min-width: 0; width: 100%; padding: 7px 26px 7px 9px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--panel); color: var(--ink);
  font: inherit; font-size: .86rem; cursor: pointer; outline: none;
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
}
body.v2rail .v2-mkpick select:hover { border-color: var(--ink-dim); }
body.v2rail .v2-mkcaret {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  pointer-events: none; color: var(--ink-faint); font-size: .68rem; line-height: 1;
}
/* a scoped board is stated the same way an active tab stated it — the menu
   showing a market name is the answer to "why am I looking at 300 rows and not
   21,000", so it carries the accent rather than looking like an idle control */
body.v2rail .v2-mkpick.on select {
  border-color: rgba(232,161,60,.55); background: rgba(232,161,60,.09);
}
body.v2rail .v2-mkedbtn {
  flex: none; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
  color: var(--ink-faint); font-size: .84rem; cursor: pointer; transition: .16s;
}
body.v2rail .v2-mkedbtn:hover { color: var(--accent-text); border-color: rgba(232,161,60,.55); }
body.v2rail .v2-mkpick select:focus-visible, body.v2rail .v2-mkedbtn:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
body.v2rail .v2-mked { margin-top: 10px; padding: 12px 13px 10px; }

/* ── should we bid — a chip like every other rail filter (v2VerdictChip(),
   #v2-verdict-chip), not v2Answer()'s own masthead markup. Its .v2-fchip
   needs no rules beyond what "── filters" below already gives every
   .v2-filters/.v2-fchip. Its second child, .v2-passtog ("N hidden — show"),
   is v2Answer()'s masthead button unchanged in markup and behavior — the
   "── filters" column stretches it full-width like everything else there
   (matching every other rail child, including the "Clear all" button this
   mirrors), so pin it to its own content width the same way .v2-clrall
   already is. Archive mode still hides the whole section — a verdict is a
   live-board call an archived record does not have. */
body.archmode .v2r-verdict { display: none; }
body.v2rail .v2-passtog { align-self: flex-start; margin-left: 0; }

/* Its popover is a centered modal like every other rail filter's (see
   "popovers are centered modals" below), so it no longer opens in flow. */

/* ── search — the box fills the column instead of a wide flex row ─────────
   THE INPUT GETS THE WHOLE LINE. The row layout seats the icon, the input,
   Search, ✕ and a "/" badge on one line; at 320px that left the input ~130px
   of ~300px and the box was unusable to type a phrase into. Here the box holds
   the input and its ✕ only, and Search sits on its own row with the ⓘ. See
   renderBrowseRail for the reasoning, including why the "/" badge is gone. */
body.v2rail .v2-cmd { display: block; margin-bottom: 0; }
body.v2rail .v2-qfield { min-width: 0; }
body.v2rail .v2-qbox { padding: 0 10px; }
body.v2rail .v2-qbox .ic { margin-right: 7px; font-size: .9rem; }
body.v2rail .v2-qbox input { font-size: .92rem; padding: 10px 0; }
/* the row layout's Search/Save buttons carry margin-left for their inline
   seat on the box; here they are flex items of their own row */
body.v2rail .v2r-qrow { display: flex; align-items: center; gap: 7px; margin-top: 7px; }
body.v2rail .v2r-qrow .v2-qgo,
body.v2rail .v2r-qrow .v2-qadd { margin-left: 0; }
body.v2rail .v2r-qi {
  margin-left: auto; flex: none; width: 21px; height: 21px; border-radius: 50%;
  border: 1px solid var(--line); background: none; color: var(--ink-faint); cursor: pointer;
  font-family: var(--serif, Georgia, serif); font-style: italic; font-size: .78rem; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center; transition: .16s;
}
body.v2rail .v2r-qi:hover { color: var(--ink); border-color: var(--ink-faint); }
body.v2rail .v2r-qi.open { color: var(--accent-text); border-color: var(--accent); }

/* the syntax help folds into that ⓘ — instructions are read once and then in
   the way, and the keyword receipt below demonstrates the same grammar on the
   reader's own query. It is a BOX when open, not a loose line, so it reads as
   the ⓘ's answer rather than as more chrome under the field. */
body.v2rail .v2r-qhelp { display: none; }
body.v2rail .v2r-qhelp.open {
  display: block; margin: 7px 0 0; padding: .6rem .7rem; font-size: .72rem; line-height: 1.5;
  background: var(--panel); border: 1px solid var(--line); border-radius: 11px;
}
body.v2rail .v2r-qhelp.open > * { display: block; margin-bottom: .3rem; }
body.v2rail .v2r-qhelp.open > *:last-child { margin-bottom: 0; }
body.v2rail .v2r-qkey { color: var(--ink-faint); border-top: 1px solid var(--line-soft); padding-top: .4rem; }
body.v2rail .v2r-qkey b { font-family: var(--mono); color: var(--ink-dim); }
/* #v2-qpend is live STATUS, not instruction — it never folds, and it reads
   left-to-right on its own line here rather than right-aligned in a help row */
body.v2rail .v2-qpend.on { text-align: left; margin: 7px 2px 0; font-size: .72rem; white-space: normal; }

/* ── the keyword receipt ──────────────────────────────────────────────────
   A collapsed pill stating what the box parsed, opening a list of the terms
   themselves, each removable. Built to the rail mockup's spec; see v2KwPaint
   in app.js for why it exists and why it is rail-only. The popover here is
   INLINE (part of the rail's own flow, pushing the filters down) rather than
   floating like .v2-pop — a 22-term list is exactly the thing that should
   scroll with the rail rather than hang over the results, and it means this
   one cannot be clipped by the rail no matter how long it grows. */
.v2-kwrecpt { display: none; }
.v2-kwrecpt.on { display: block; margin-top: .45rem; }
.v2-kwpill {
  display: inline-flex; align-items: center; gap: .4rem; max-width: 100%;
  background: var(--row); border: 1px solid var(--line); border-radius: 100px;
  padding: .3rem .55rem .3rem .65rem; font-size: .74rem; color: var(--ink);
  cursor: pointer; transition: border-color .15s;
}
.v2-kwpill:hover { border-color: var(--ink-faint); }
.v2-kwpill.open { border-color: var(--accent); }
.v2-kwpill .k {
  font-family: var(--mono); font-size: .58rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--accent-text); flex: none;
}
.v2-kwpill .s { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-kwpill .car { color: var(--ink-faint); font-size: .6rem; flex: none; transition: transform .2s; }
.v2-kwpill.open .car { transform: rotate(180deg); }

.v2-kwpop {
  width: 100%; margin-top: .4rem; background: var(--panel); border: 1px solid var(--line);
  border-radius: 11px; padding: .65rem .7rem; display: none;
}
.v2-kwpop.open { display: block; }
.v2-kwg { margin-bottom: .5rem; }
.v2-kwg:last-child { margin-bottom: 0; }
.v2-kwgl {
  font-family: var(--mono); font-size: .58rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-faint); margin-bottom: .3rem;
}
.v2-kwts { display: flex; flex-wrap: wrap; gap: .35rem; }
.v2-kwt {
  display: inline-flex; align-items: center; gap: .35rem; max-width: 100%;
  background: var(--row); border: 1px solid var(--line-soft); border-radius: 100px;
  padding: .22rem .3rem .22rem .55rem; font-size: .74rem; color: var(--ink-dim);
  overflow: hidden;
}
/* The term is its own element with min-width:0 so it can shrink. An unbreakable
   token (a URL, a long solicitation number) has min-content width, and as an
   anonymous flex item it refused to shrink and pushed the flex:none ✕ outside
   the chip's overflow:hidden box — measured, the button laid out at x=411 in a
   chip ending at x=289, i.e. rendered but unclickable by mouse. */
.v2-kwt > .t { min-width: 0; overflow-wrap: anywhere; }
.v2-kwt.excl { color: var(--warn); }
.v2-kwt > button {
  color: var(--ink-faint); font-size: .68rem; padding: .05rem; background: none; border: 0;
  cursor: pointer; line-height: 1; flex: none;
}
.v2-kwt > button:hover { color: var(--warn); }
.v2-kwt.excl > button:hover { color: var(--ink); }

/* ── filters — one chip per row, full width, no fold ─────────────────────
   .v2-fchip keeps its own internal layout (label · value · caret); only its
   own width and how it sits in the column change. */
body.v2rail .v2-filters { flex-direction: column; align-items: stretch; gap: 6px; margin-bottom: 0; }
/* the Match in chip, under the Keywords chip — laid out as that column is */
body.v2rail .v2-kwin { flex-direction: column; align-items: stretch; gap: 6px; margin: 6px 0 0; }
body.v2rail .v2-seg { align-self: stretch; }
body.v2rail .v2-seg button { flex: 1; text-align: center; }
body.v2rail .v2-fchip { width: 100%; justify-content: space-between; }
body.v2rail .v2-fchip .fv { flex: 1; text-align: right; overflow: hidden; text-overflow: ellipsis; }
/* the fold button/shelf never render here (v2RailFilters never calls
   v2FoldRow) — this is a defensive backstop only, not load-bearing */
body.v2rail .v2-fold { display: none; }
body.v2rail .v2-clrall { align-self: flex-start; padding-left: 2px; }

/* ── popovers are CENTERED MODALS in the rail (2026-10-08) ─────────────────
   Owner: "when clicking in, there needs to be a big center modal" — the look
   of the Buyer / PSC / NAICS / Source sheet (assets/js/hitch-tree.js, .ht-sheet)
   for every other filter: Timing, What's new, Value, Set-aside, Notice type,
   Place, Security, Proposal, Only show, Should we bid, the company's fields and
   keyword fields. Same panel markup (.v2-pop, built by v2Pop) and the same
   wiring (v2WirePop) — only the placement changes: fixed and centered over a
   scrim, sized like the tree sheet, so nothing in the rail can clip it and it
   can never cover the chip that opened another one. v2PopFit() leaves a rail
   panel alone. The scrim is the chip's own child, so a click on it is the
   chip's toggle and closes the panel; Escape and Done close it too. */
/* .rail is a stacking context (opps.css: sticky, z-index 100) under the
   sticky .topbar (105), so a modal inside it would sit under the top bar; the
   class is set by v2FiltersRerender() only while a rail popover is open. */
body.v2rail.v2popopen .rail { z-index: 120; }
body.v2rail .v2-popscrim {
  position: fixed; inset: 0; z-index: 8990; background: rgba(0,0,0,.52); cursor: default;
}
body.v2rail .v2-pop {
  position: fixed; inset: 0; margin: auto; z-index: 8991;
  width: min(560px, 94vw); height: fit-content; max-height: 86vh;
  padding: 0 18px; border-radius: 14px; background: var(--bg-soft);
  font-size: .9rem; cursor: default;
}
body.v2rail .v2-pop.wide { width: min(640px, 94vw); }
body.v2rail .v2-pop.right { left: 0; right: 0; }
body.v2rail .v2-pop-h {
  margin: 0 -18px 10px; padding: 15px 18px 13px; border-bottom: 1px solid var(--line);
  font-family: var(--sans, inherit); font-size: 1rem; font-weight: 500; letter-spacing: normal;
  text-transform: none; color: var(--ink);
}
body.v2rail .v2-pop-h .r {
  font-family: var(--mono); font-size: .66rem; font-weight: 400; letter-spacing: .06em; text-transform: uppercase;
}
body.v2rail .v2-pop .v2-opts {
  max-height: none; overflow-y: auto; margin: 0 -6px;
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}
body.v2rail .v2-pop .v2-opt { padding: 9px 10px; font-size: .9rem; }
body.v2rail .v2-pop-foot {
  margin: 12px -18px 0; padding: 12px 18px; border-top: 1px solid var(--line);
}
/* Done is the panel's primary action, as Apply is on the tree sheet */
body.v2rail .v2-pop-foot [data-v2done] {
  background: var(--accent); color: #1a1206; border-radius: 999px; padding: 8px 22px;
  font-family: var(--sans, inherit); font-size: .84rem; letter-spacing: normal; text-transform: none;
}
body.v2rail .v2-pop-foot [data-v2done]:hover { filter: brightness(1.07); color: #1a1206; }
body.v2rail .v2-pop-foot [data-v2clear] {
  border: 1px solid var(--line); border-radius: 999px; padding: 7px 16px;
  font-family: var(--sans, inherit); font-size: .82rem; letter-spacing: normal; text-transform: none;
  color: var(--ink-dim);
}

/* the Keywords section: its chip, then the documents switch and Save as market */
body.v2rail .v2r-kw .v2-qdocsrow { margin-top: 8px; }
body.v2rail .v2r-kw .v2-qadd { margin: 8px 0 0; }

/* ── COLOR, SIZE AND MOTION (owner, 2026-10-08: "more dynamic and colorful
   and larger font") ────────────────────────────────────────────────────────
   The rail's one color, `--hue`, is set below. The chip
   wears it as a left edge and on its label; a SET chip is tinted with it; and
   because the chip's popover is its child, the centered modal inherits the
   same color for its top edge, ticks and Done button. Light theme uses a
   darker gold so labels stay readable on paper. Motion is short (≤ .2s) and off under reduced motion
   (the prefers-reduced-motion block below already zeroes every transition and
   animation in the rail). */
/* ONE color for the whole rail (owner, 2026-10-08: "too many colors in the
   left rail") — the platform accent, darker on paper. A chip still shows its
   edge and label in it, and a set chip is tinted, so what is narrowing the
   board stands out; there is no per-filter color. */
body.v2rail { --hue: var(--accent); }
body.light.v2rail { --hue: #9a6510; }

/* the rail's own type, one size up */
body.v2rail .v2r-lbl { font-size: .74rem; letter-spacing: .16em; margin-bottom: 10px; }
body.v2rail .v2r-sub { font-size: .8rem; }
body.v2rail .v2r-ttl { font-family: var(--sans, inherit); font-size: 1.05rem; font-weight: 600;
  letter-spacing: .01em; text-transform: none; color: var(--ink); }
body.v2rail .v2r-scope button { font-size: .78rem; padding-top: 9px; padding-bottom: 9px; }
body.v2rail .v2-mkpick select { font-size: .98rem; padding-top: 9px; padding-bottom: 9px; }

/* chips: a colored edge, a colored label, larger type, a lift on hover */
body.v2rail .v2-fchip {
  font-size: 1rem; padding: 11px 14px 11px 16px; border-radius: 12px;
  border-left: 4px solid var(--hue);
  transition: border-color .15s, background .15s, box-shadow .15s;
}
/* ⛔ NO transform ON A CHIP, not even a hover lift: a transformed element is the
   containing block for its position:fixed children, and the chip's popover is
   its child — a lifted chip trapped the centered modal inside the rail. */
body.v2rail .v2-fchip .fl {
  font-size: .76rem; letter-spacing: .1em; color: var(--hue); font-weight: 500;
}
body.v2rail .v2-fchip .fv { font-size: 1rem; }
body.v2rail .v2-fchip .car { font-size: .7rem; color: var(--hue); transition: transform .15s; }
body.v2rail .v2-fchip:hover {
  border-color: var(--hue); color: var(--ink);
  background: color-mix(in srgb, var(--hue) 7%, var(--panel));
  box-shadow: 0 6px 18px -8px var(--hue);
}
body.v2rail .v2-fchip:hover .car { transform: translateY(1px); }
body.v2rail .v2-fchip.set {
  border-color: color-mix(in srgb, var(--hue) 60%, transparent);
  border-left-color: var(--hue);
  background: color-mix(in srgb, var(--hue) 13%, var(--panel));
}
body.v2rail .v2-fchip.set .fl { color: var(--hue); }
body.v2rail .v2-fchip.set .fv { font-weight: 500; }
body.v2rail .v2-fchip.open { border-color: var(--hue); box-shadow: 0 0 0 3px color-mix(in srgb, var(--hue) 22%, transparent); }
body.v2rail .v2-fchip .badge { background: var(--hue); }
body.v2rail .v2-fchip:focus-visible { outline: 2px solid var(--hue); outline-offset: 2px; }

/* the modal: the chip's hue on its top edge, larger type, ticks and Done in the hue */
body.v2rail .v2-pop {
  border-top: 4px solid var(--hue);
  animation: v2modalIn .18s var(--ease, ease) both;
}
body.v2rail .v2-popscrim { animation: v2scrimIn .18s ease both; }
@keyframes v2modalIn { from { opacity: 0; transform: translateY(10px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes v2scrimIn { from { opacity: 0; } to { opacity: 1; } }
body.v2rail .v2-pop-h { font-size: 1.2rem; font-weight: 600; }
body.v2rail .v2-pop-h .r { color: var(--hue); font-size: .72rem; }
body.v2rail .v2-pop .v2-opt { font-size: 1rem; padding: 10px 11px; }
body.v2rail .v2-pop .v2-opt:hover { background: color-mix(in srgb, var(--hue) 10%, var(--panel2)); }
body.v2rail .v2-pop .v2-opt .bx { width: 18px; height: 18px; font-size: .7rem; border-radius: 5px; }
body.v2rail .v2-pop .v2-opt.on .bx { background: var(--hue); border-color: var(--hue); }
body.v2rail .v2-pop .v2-opt .cn { font-size: .8rem; }
body.v2rail .v2-pop .v2-opt .lab .sub { font-size: .74rem; }
body.v2rail .v2-pop .v2-opt-sec { font-size: .72rem; color: var(--hue); }
body.v2rail .v2-pop-search { font-size: .98rem; padding: 10px 12px; }
body.v2rail .v2-pop-search:focus { border-color: var(--hue); }
body.v2rail .v2-pop-foot [data-v2done] { background: var(--hue); font-size: .92rem; padding: 9px 24px; }
body.v2rail .v2-pop-foot [data-v2clear] { font-size: .88rem; }
body.v2rail .v2-pop-foot [data-v2clear]:hover { border-color: var(--hue); color: var(--ink); }

/* the keyword window, opened from the rail: the Keywords hue, one size up */
body.v2rail .ws-mb__box { border-top: 4px solid #f2b84b; animation: v2modalIn .2s var(--ease, ease) both; }
body.light.v2rail .ws-mb__box { border-top-color: #8a5a10; }
body.v2rail .ws-mb__head h2 { font-size: 2.1rem; }
body.v2rail .ws-mb__intro, body.v2rail .ws-mb__lbl { font-size: 1rem; }
body.v2rail .ws-mb__tin { font-size: 1.02rem; }
body.v2rail .ws-mb__chip { font-size: 1rem; }

/* ── READABILITY (owner, 2026-10-08: "still too hard to read and too small.
   You solved this on the budget search interface") ─────────────────────────
   What the Budget Workstation does and this page did not: text at weight 400
   (this page inherited Inter 300 from opps.css, a hairline on dark), secondary
   text in the SANS face at --ink-dim rather than 10px monospace at --ink-faint,
   and labels as bold sans rather than spaced-out mono. Monospace stays only
   where it carries digits that line up: dates, values, counts, solicitation
   numbers. Scoped to body.v2rail — the default board and the pipeline board;
   the row layout (?rail=0) is left as it was. Measured before: table body
   12.5px/300, column heads 9.9px mono, row metadata 10.9px mono in --ink-faint. */
body.v2rail { font-weight: 400; }
body.v2rail input, body.v2rail select, body.v2rail textarea, body.v2rail .v2-pop { font-weight: 400; }

/* rail labels: bold sans, like the Workstation's section titles */
body.v2rail .v2r-lbl { font-family: var(--sans, inherit); font-weight: 700; font-size: .78rem; letter-spacing: .1em; }
body.v2rail .v2r-sub { font-weight: 400; color: var(--ink-dim); }
body.v2rail .v2-fchip .fl { font-family: var(--sans, inherit); font-weight: 700; font-size: .8rem; letter-spacing: .06em; }
body.v2rail .v2-fchip .fv { font-weight: 500; }
body.v2rail .v2r-scope button { font-family: var(--sans, inherit); font-weight: 600; font-size: .86rem; letter-spacing: .02em; text-transform: none; }
body.v2rail .v2-mkl { font-family: var(--sans, inherit); font-size: .8rem; letter-spacing: normal; text-transform: none; color: var(--ink-dim); }
body.v2rail .v2-qdocs { font-size: .9rem; }
body.v2rail .v2-qdocsr { font-family: var(--sans, inherit); font-size: .82rem; color: var(--ink-dim); }
body.v2rail .v2-pop .v2-opt { color: var(--ink); }
body.v2rail .v2-pop .v2-opt .lab .sub { font-family: var(--sans, inherit); font-size: .8rem; color: var(--ink-dim); }
body.v2rail .v2-pop-h .r { font-family: var(--sans, inherit); font-weight: 600; font-size: .8rem; letter-spacing: .02em; }
body.v2rail .v2-pop .v2-opt-sec { font-family: var(--sans, inherit); font-weight: 700; font-size: .78rem; letter-spacing: .06em; }
body.v2rail .v2-vr-hint { font-family: var(--sans, inherit); font-size: .8rem; color: var(--ink-dim); }

/* the results header */
body.v2rail .v2-sortsel { font-family: var(--sans, inherit); font-size: .82rem; font-weight: 600; letter-spacing: .04em; color: var(--ink-dim); }
body.v2rail .v2-sortsel select { font-family: var(--sans, inherit); font-size: .92rem; font-weight: 500; }
body.v2rail .v2-views button { font-family: var(--sans, inherit); font-size: .84rem; font-weight: 600; letter-spacing: .04em; }
body.v2rail .v2-tabtog { font-family: var(--sans, inherit); font-size: .82rem; font-weight: 600; letter-spacing: .04em; }
body.v2rail .v2-pager { font-size: .92rem; }

/* the table */
body.v2rail .v2-tbl thead th {
  font-family: var(--sans, inherit); font-weight: 700; font-size: .76rem; letter-spacing: .08em; color: var(--ink-dim);
}
body.v2rail .v2-tbl tbody td { font-size: .95rem; }
body.v2rail .v2-title { font-size: 1.1rem; font-weight: 600; }
body.v2rail .v2-tbl tbody tr[data-verdict=nobid] .v2-title { font-size: 1.02rem; }
body.v2rail .v2-why { font-size: .92rem; color: var(--ink-dim); }
body.v2rail .v2-sub { font-size: .82rem; color: var(--ink-dim); }
body.v2rail .v2-buyer { font-size: .95rem; color: var(--ink); }
body.v2rail .v2-buyer i { font-family: var(--sans, inherit); font-size: .82rem; color: var(--ink-dim); }
body.v2rail .v2-due .abs { font-size: .8rem; color: var(--ink-dim); }
body.v2rail .v2-none { font-family: var(--sans, inherit); font-size: .9rem; color: var(--ink-dim); }
body.v2rail .v2-call .sc { font-size: .78rem; color: var(--ink-dim); }
body.v2rail .v2-flag, body.v2rail .v2-ceil { font-size: .72rem; }
body.v2rail .v2-state { font-family: var(--sans, inherit); font-size: .82rem; font-weight: 600; }
body.v2rail .v2-act { font-family: var(--sans, inherit); font-size: .8rem; font-weight: 600; letter-spacing: .02em; text-transform: none; }
body.v2rail .v2-found { font-family: var(--sans, inherit); font-size: .8rem; color: var(--ink-dim); letter-spacing: normal; }
body.v2rail .v2-pass, body.v2rail .v2-passed, body.v2rail .v2-passtog {
  font-family: var(--sans, inherit); font-size: .8rem; letter-spacing: normal;
}
body.v2rail .v2-passed { color: var(--ink-dim); }

/* the leftover small and dim bits: the no-date state, its RFP estimate, the
   activity date, the documents flag, the Columns picker and the source line */
body.v2rail .v2-due { color: var(--ink-dim); }
body.v2rail .v2-none .abs, body.v2rail .v2-none .abs.est, body.v2rail .v2-due .abs { font-size: .8rem; }
body.v2rail .v2-flag { font-size: .76rem; }
body.v2rail .colmenu .colfoot .v2-lk { color: var(--ink-dim); }
body.v2rail .v2-tbl .c-activity span { color: var(--ink-dim); }
body.v2rail .v2-flag.doc { color: var(--ink-dim); }
body.v2rail .colbtn { font-family: var(--sans, inherit); font-size: .84rem; font-weight: 600; letter-spacing: .02em; padding: 7px 12px; }
body.v2rail .colmenu .colsec { font-family: var(--sans, inherit); font-size: .76rem; font-weight: 700; color: var(--ink-dim); }
body.v2rail .colopt { font-size: .9rem; color: var(--ink); }
body.v2rail .colmv { font-size: .7rem; color: var(--ink-dim); }
body.v2rail .colmenu .v2-lk { font-family: var(--sans, inherit); font-size: .8rem; letter-spacing: normal; text-transform: none; }
body.v2rail .srcline { font-size: .84rem; color: var(--ink-dim); }

/* cards */
body.v2rail .v2-ctitle { font-size: 1.1rem; font-weight: 600; }
body.v2rail .v2-cbuyer { font-family: var(--sans, inherit); font-size: .9rem; }
body.v2rail .v2-cwhy { font-size: .95rem; }
body.v2rail .v2-cinc { font-family: var(--sans, inherit); font-size: .86rem; }
body.v2rail .v2-ccall .sc { font-size: .78rem; color: var(--ink-dim); }

@media (max-width: 980px) {
  /* the rail stacks ABOVE the results rather than disappearing — unlike the
     row layout, which has no narrow-viewport treatment for its filter row
     at all (opps-v2.css's one @media rule only touches the answer band and
     the cards grid), this at least keeps every control reachable on a
     phone, in exchange for scrolling past it to reach the first result. */
  body.v2rail .workspace { display: block; }
  body.v2rail .rail {
    display: block; width: auto; border-right: none; border-bottom: 1px solid var(--line-soft);
    padding: 1rem 1rem 1.2rem; max-height: none; overflow-y: visible;
  }
  body.v2rail .main { padding: 14px 14px 60px; }
  /* the popovers stay centered modals here too — fixed to the viewport, so
     the page scrolling under them does not move them off their content. */
}
@media (prefers-reduced-motion: reduce) {
  body.v2rail * { animation: none !important; transition: none !important; }
}

/* the scope switch, relocated from #main's results header into the rail so it
   reaches the pipeline board too (see railFilterMarkup). It is a segmented
   control in a 320px column rather than one item in a wide toolbar, so it
   takes the full width and splits it evenly — the .v2-views styling it keeps
   is otherwise unchanged. */
body.v2rail .v2r-scope { display: flex; width: 100%; }
body.v2rail .v2r-scope button { flex: 1; text-align: center; }
/* a second label inside a section (Market's "Showing") — the section's own
   label has no top margin because it opens the box; this one needs one. */
body.v2rail .v2r-lbl2 { margin-top: 14px; }

/* ── the company's own fields, in the rail (migration 0102) ────────────────
   .v2r-cfnote is the section's note line. Since 2026-09-02 it carries only
   the "Nothing filled in yet" state; the scope caption ("Within the N cards
   on this board.") and the "named by your admin" tag beside the label were
   removed at the owner's request (v2CfRail). */
/* ⚠ --ink-dim, NOT --ink-faint. Measured against the rail's own ground:
   --ink-faint puts both of these at 3.49:1, under the 4.5 floor for text this
   size, while the rail's own shipped .v2r-lbl sits at 9. The caption is the
   sentence that makes every count below it honest and the label is the field's
   name — neither is decoration that may recede. --ink-dim measures 6.6. */
.v2r-cfnote { font-size: .68rem; line-height: 1.5; color: var(--ink-dim); margin: -2px 0 10px; }
/* The range lives INSIDE the rail's own popover now, so it inherits the
   popover's ground and needs only its own two-up layout. The inputs carry
   their own rules and NOT `.fsel`, whose only rule is `select.fsel` — an
   <input> given that class gets the name and none of the appearance. */
.v2-pop .v2-cfrange { display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
  padding: 2px 0 6px; }   /* .v2-pop already pads 12px — this only spaces the two inputs */
.v2-pop .v2-cfrange label { display: block; min-width: 0; }
.v2-pop .v2-cfrange label span { display: block; font-family: var(--mono); font-size: .55rem;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-dim); margin-bottom: 4px; }
.v2-pop .v2-cfrange input { width: 100%; min-width: 0; background: var(--field);
  color: var(--ink); border: 1px solid var(--line); border-radius: 8px;
  padding: 6px 9px; font-size: .78rem; font-family: var(--sans); font-weight: 300; }
.v2-pop .v2-cfrange input:focus { outline: none; border-color: var(--accent); }
.v2r-cfwarn { font-size: .68rem; line-height: 1.5; color: var(--accent-text); margin: 10px 0 0; }
