/* ============================================================================
   cand-rail.css — stylesheet for app/cand-rail.jsx.

   Every class is prefixed `.e8-cand-rail-`. The pre-existing `.e8-cand-*`,
   `.e8-rrail-*` and `.e8-applrow` rules stay in app/app.css; nothing was moved
   out of it in this pass.

   ONE EXCEPTION, AND IT IS ANCHORED THE ONLY WAY AN ANCESTOR CAN BE: the sheet
   chassis at the foot of this file styles `.e8-bsheet`, which is an ANCESTOR of
   this module's markup rather than a descendant, so it cannot be reached by
   putting one of our classes in front of it. `:has(.e8-cand-rail-pane)` does the
   same job from the other end - it matches the bottom sheet ONLY while this
   rail's pane is inside it, and `.e8-cand-rail-pane` is emitted by
   CandRailDetails / CandRailActivity and nothing else. The conversation sheet
   (screens-comms.jsx), the quick-candidate sheet (screens-core.jsx:821) and every
   other record's rail are structurally out of reach. See that block's own header
   for why the fix is here at all and what belongs upstream.

   What IS here: the inline `style={{…}}` objects that came across with the
   markup. A new .jsx has an inline-style budget of 0
   (scripts/check-inline-style.mjs), so a moved inline style has to become a
   class. Each rule reproduces its inline declarations exactly - same property,
   same value - and this file is linked AFTER app.css, so a single-class rule
   here beats the single-class rule there just as the inline style used to.
   Two of them needed a modifier because the inline value was CONDITIONAL
   (`.is-spaced`, `.is-sep`); a static class alone could not express those.
   ============================================================================ */

/* was style={{ width: '100%', textAlign: 'left', margin: '6px 0' }} */
.e8-cand-rail-askbtn { width: 100%; text-align: left; margin: 6px 0; }

/* was style={{ padding: '8px 0 2px' }} — the Skills and Lists blocks in the fold */
.e8-cand-rail-foldblk { padding: 8px 0 2px; }

/* was style={{ display: 'flex', gap: 5, flexWrap: 'wrap', marginBottom: prof.campaigns.length ? 8 : 0 }} */
.e8-cand-rail-listpills { display: flex; gap: 5px; flex-wrap: wrap; margin-bottom: 0; }
.e8-cand-rail-listpills.is-spaced { margin-bottom: 8px; }

/* was style={{ fontSize: 'var(--ui-text-sm)', fontWeight: 500, minWidth: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }} */
.e8-cand-rail-campname { font-size: var(--ui-text-sm); font-weight: 500; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* pending-updates-from-candidate block */
.e8-cand-rail-pendhint { font-size: var(--ui-text-xs); color: var(--ui-text-secondary); line-height: 1.45; margin-bottom: 8px; }
.e8-cand-rail-pendlist { display: grid; gap: 6px; }
.e8-cand-rail-pendcard { border: 1px solid var(--ui-border); border-radius: var(--ui-radius-md); padding: 8px 10px; background: var(--ui-warning-tint); }

/* was style={{ display: 'inline-flex' }} on a <Row>. .e8-ui-row sets display:flex at
   app.css:2736 (one class); this file is later, so the override lands the same way the
   inline style did. */
.e8-cand-rail-inline { display: inline-flex; }

/* ICON RULES CARRY THE CLASS TWICE ON PURPOSE. `.elev8 .material-symbols-outlined`
   (product.css:267) is a (0,2,0) rule that sets EVERY product icon to
   `font-size: var(--ui-icon-md); color: var(--ui-text-secondary)`, and its own comment says
   it is written that way to beat `.elev8 span`. A plain `.e8-cand-…` class is (0,1,0) and
   loses to it no matter how late the file is linked - which is exactly what happened here on
   the first attempt: five icons silently went to 20px grey, and only the computed-style A/B
   caught it because the geometry knock-on was 2-4px. `.material-symbols-outlined.e8-cand-…`
   is (0,2,0), ties, and wins on order. It is also the shape app.css already uses
   (`.material-symbols-outlined.e8-recrow-ic`, `.material-symbols-outlined.e8-cmdk-keep`). */

/* icons that set BOTH size and colour, so they are not "icon size only" and had to move */
.material-symbols-outlined.e8-cand-rail-chev { font-size: var(--ui-icon-md); color: var(--ui-icon-quiet); }
.material-symbols-outlined.e8-cand-rail-fileic { font-size: var(--ui-icon-sm); color: var(--ui-text-secondary); flex: none; }

/* the "All notes · N" / "All files · N" section actions */
.e8-cand-rail-more { font-size: var(--ui-text-xs); border: 0; background: transparent; padding: 0; font-family: inherit; }

/* note rows. The padding/border half of this rule moved into the shared row grammar
   at the foot of the file (pieces 9+10) so all four rail lists draw one chassis; what
   is left is the part that is only true of a note row. The `.is-sep` MODIFIER stays -
   it is a conditional the JSX still emits (`borderTop: i ? … : 'none'`) - and its
   declaration now lives with the group so the two cannot draw a 2px line together. */
.e8-cand-rail-noterow { cursor: pointer; }
.e8-cand-rail-notetitle { font-size: var(--ui-text-sm); font-weight: 500; line-height: 1.35; }
.e8-cand-rail-notepts { font-size: var(--ui-text-xs); color: var(--ui-text-secondary); margin-top: 2px; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* pipeline rows */
.e8-cand-rail-rowtitle { display: block; font-size: var(--ui-text-sm); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.e8-cand-rail-subrow { align-items: flex-start; }

/* files summary rows */
.e8-cand-rail-filename { min-width: 0; font-size: var(--ui-text-sm); color: var(--ui-daybreak-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Activity pane: the empty-state padding */
.e8-cand-rail-emptypad { padding: 18px 0; }

/* ============================================================================
   PIECES 9 + 10 — the Details rhythm, and ONE block grammar for Notes / Tasks /
   Pipeline / Files.

   SCOPE. `.e8-cand-rail-pane` is the div CandRailDetails / CandRailActivity now
   return instead of a bare Fragment. It exists so these rules can reach the two
   blocks this module RENDERS but does not own the markup of - RecordTasksRail
   (app/screens-tasks.jsx, `.e8-rtk-*`) and RailFact/RailSection
   (app/record-shell.jsx, `.e8-rrail-*`). Every selector below starts
   `.e8-cand-rail-`, and none of them can reach outside this rail: the wrapper is
   emitted by these two components and nothing else. That is the structural-reach
   argument for "unchanged elsewhere" - `.e8-applrow` in particular is also used
   by the expanded stage strip in cand-record.jsx, which is outside the wrapper
   and therefore untouched.

   ONE SIZE QUERY, AND IT IS A @container ON THIS PANE. Everything else here is a
   type/ink/rhythm rule that wants to be the same at every width. The exception is
   the section COLUMNS below: the pane is 248-480px as an inline rail and 794px
   inside the tablet bottom sheet, and one column of 794px-wide rows is not the same
   design. `@container e8rec` cannot express that - the sheet is a SIBLING of
   .e8-recordbody, outside the named container entirely - and a viewport `@media` is
   the bug CLAUDE.md documents twice, because the rail resizes this pane without the
   viewport moving at all. So the pane declares its OWN container and asks about its
   own inline size, which is the one measurement that is true in both chassis.
   ============================================================================ */

/* WAS `max-width: 560px`. The cap was the first answer to "a label/value row 780px
   wide is two columns that happen to share a line", and it half-worked: it did stop
   the leader gap growing without limit, and it left 234px of a 794px sheet (29.5%)
   permanently blank while the SAME sheet had to scroll 910px of content through
   689px. Capping the row was treating the width as a hazard. Two columns spend it,
   measured at 834 after: pane 560 -> 790 of a 794px body (dead space 234 -> 4), the
   six sections paired into 376px columns, the widest label..value gap 448 -> 274 -
   the rhythm of a wide desktop rail, which is the shape nobody has complained about
   - and content height 910 -> 573, which now fits the body with no inner scroll at
   all. `container-type: inline-size` replaces the cap rather than joining it: a
   capped container can never report more than 560px, so the query would never fire.
   NAMED, and the name matters in the other direction too. Adding a container here
   makes this pane the nearest container for anything inside it, so an UNNAMED query
   in another file could start measuring this pane instead of the box its author
   meant. Checked, not assumed: the three unnamed blocks in play (app.css:1416,
   cand-header.css:278, cand-record.css) select only `.e8-cand-overview`,
   `.e8-cand-header-*` and `.e8-cand-rec-*`, none of which render inside this rail. */
/* padding-right clears the OVERLAY scrollbar. .e8-rrail-body scrolls with only 2px
   of right padding, and it is an overlay scrollbar here - measured offsetWidth 350 ==
   clientWidth 350, i.e. it reserves no space and paints ON the content. Every value in
   this pane is right-aligned, so the scrollbar sat on the right edge of "Memphis, TN"
   and "verified 3 mo ago". Right only: the labels on the left have nothing to clear. */
.e8-cand-rail-pane { container-name: e8candrail; container-type: inline-size; padding-right: 10px; }

/* ---- Piece 9: the Details pane ------------------------------------------- */

/* The value column is --ui-text-base/600 for every fact EXCEPT freshness, which
   FreshnessMenu renders through `.e8-cand-meta` - `font-size: var(--ui-text-sm);
   color: var(--ui-text-tertiary)` at app.css:5998. So the one fact that tells you
   whether to believe the other nine was the quietest thing in the pane: 12.5px
   tertiary (rgb(156,156,166), 2.72:1 on the white rail) next to 13.5px primary
   (15.7-17:1). After: 13.5px --ui-text, measured 16.97:1 light / 15.69:1 dark.
   Weight 500 rather than the column's 600 - it is a state plus a timestamp, not a
   value someone typed. The tone dot keeps its own colour, so the state is still
   carried by hue AND by the word, never by hue alone. */
.e8-cand-rail-freshv .e8-cand-meta {
  font-size: var(--ui-text-base); font-weight: 500; color: var(--ui-text);
}
.e8-cand-rail-freshv .e8-cand-meta i { width: 7px; height: 7px; border-radius: 4px; }
.e8-cand-rail-freshv .e8-freshbtn:hover { color: var(--ui-text); }

/* The section head's right slot carries the affordance sentence instead of the
   footnote 840px below the facts. Quiet enough to read as instruction, not data. */
.e8-cand-rail-edithint {
  font-size: var(--ui-text-xs); font-weight: 400; letter-spacing: 0; text-transform: none;
  color: var(--ui-text-tertiary);
  display: inline-flex; align-items: center; gap: 4px;
}
/* The legend glyph IS the mark it describes: same ligature, same size, same token as the
   ::after pencil on every editable value below (see THE EDIT AFFORDANCE). If those two ever
   disagree the sentence stops being true, so they read from the same two variables.
   Double class for the reason set out at the head of this file - product.css:267. */
.material-symbols-outlined.e8-cand-rail-hintic {
  font-size: var(--ui-icon-xs); color: var(--ui-icon-quiet); flex: none;
}

/* THE EDIT AFFORDANCE. Before: a 1px dashed underline in --ui-border-strong -
   1.32:1 against the rail, i.e. invisible at a glance - plus one sentence in a
   footnote ~840px further down the scroller. The pencil is PERSISTENT, not
   hover-revealed: this rail renders inside the bottom sheet on touch, where there
   is no hover to discover anything with. Hover/focus only recolours it to accent.
   Content is a Material Symbols ligature, so the axis settings from
   product.css:267 have to be restated here - a pseudo-element is not matched by
   `.elev8 .material-symbols-outlined`, and without 'wght' it renders at the
   variable font's default, visibly heavier than every real icon beside it. */
.e8-cand-rail-pane button.e8-rrail-fact-v.is-edit::after {
  content: 'edit';
  font-family: 'Material Symbols Outlined';
  font-variation-settings: 'wght' 200, 'FILL' 0, 'GRAD' 0;
  font-optical-sizing: auto;
  font-feature-settings: 'liga';
  -webkit-font-feature-settings: 'liga';
  font-size: var(--ui-icon-xs);
  line-height: 1;
  /* NO OPACITY, AND NOT THE TERTIARY TEXT TOKEN. This glyph IS the affordance, so it
     is held to 3:1 (non-text control), and both of the first two attempts failed it.
     Composited ratios MEASURED against the sheet's own background, light then dark:
       tertiary @ .45   2.53 / 2.10      the original
       tertiary @ .62   1.77 / 4.10      "fixed" for dark, quietly broke light
       tertiary @ 1     2.72 / 5.45      alpha was never the whole problem -
                                         --ui-text-tertiary is rgb(156,156,166) and
                                         cannot reach 3:1 on white at ANY strength
       icon-quiet @ 1   3.41 / 7.06      passes both
     Light is the reading that caught it, and only because it was taken: a comment
     here had already asserted "light was never the problem, 4.06:1" from arithmetic
     that was never run against the live CSSOM. --ui-icon-quiet is the token named for
     exactly this - a quiet icon - and being quiet is its job, not an alpha's. */
  color: var(--ui-icon-quiet);
  margin-left: 6px;
  vertical-align: -1px;
}
/* Dark carries the R127 stroke weight - a pseudo-element is not matched by the
   :root[data-theme=dark] .material-symbols-outlined rule that supplies it elsewhere. */
:root[data-theme="dark"] .e8-cand-rail-pane button.e8-rrail-fact-v.is-edit::after {
  font-variation-settings: 'wght' 300, 'FILL' 0, 'GRAD' 0;
}
.e8-cand-rail-pane button.e8-rrail-fact-v.is-edit:hover::after,
.e8-cand-rail-pane button.e8-rrail-fact-v.is-edit:focus-visible::after {
  color: var(--ui-accent);
}
.e8-cand-rail-pane button.e8-rrail-fact-v.is-edit:hover { border-bottom-color: var(--ui-accent); }
/* The row itself lights up so the whole label..value span reads as one target.
   NO NEGATIVE MARGIN. The first version bled 6px each side so the tint was not a
   stripe on the ink, and it MEASURED a 6px horizontal overflow inside the bottom
   sheet at 390 (pane scrollWidth 352 vs clientWidth 346) - .e8-rrail-body computes
   overflow-x:auto from its overflow-y:auto, so that is a real scrollbar, not a
   clipped edge. A full-bleed row has nothing to bleed into; the fill runs the row's
   own width instead. */
.e8-cand-rail-pane .e8-rrail-fact:has(button.e8-rrail-fact-v.is-edit) {
  border-radius: var(--ui-radius);
}
.e8-cand-rail-pane .e8-rrail-fact:has(button.e8-rrail-fact-v.is-edit):hover {
  background: var(--ui-fill-hover);
}
/* An editable row was a 26px flex row whose only hit target was the value text -
   "Unknown — add" measured 24px tall, and the row 26px. 44px is the reference bar
   for touch, and the rail is the SAME markup inside the bottom sheet, so it was
   raised everywhere rather than behind a query that cannot see the sheet.

   THAT COMPROMISE IS OVER, because there is a discriminator that is not a query.
   `.e8-rrail.is-sheet` is set by RecordRail (record-shell.jsx:206) from the shell
   CONTEXT, not from the viewport - it is true exactly when this rail is inside the
   bottom sheet and false exactly when it is the inline desktop rail, at every
   width, including the 1000px window where the sheet appears without the viewport
   being mobile. So the two chassis can finally want different things:

     sheet   every fact row 44px, the touch floor, ALL of them - the pencil rows
             were 45px and the read-only rows 28/29, and "uneven vertical rhythm"
             is the second half of the blocker filed against this pane.
     inline  every fact row 32px. AT A GLANCE measured 261px for five pairs at
             1440x900 while PIPELINE was half-clipped and FILES entirely below the
             fold; three 45px rows were the largest single share of that.

   Both numbers are the ROW, and both are MEASURED after the fact, not derived -
   an earlier 5px landed at 41px while the comment already claimed 44, which is
   exactly the way that claim gets shipped. The edit button's padding is set to
   fill its row so the pencil's own hit box is the row, not a 22px line inside it. */
/* The row's own padding goes to 0 and the min-height carries the rhythm instead,
   so the button's padding can fill the WHOLE row rather than sitting inside it -
   4px of row padding is 4px the thumb does not get, and it also pushed the row
   past the floor it was aiming at (measured 47px rows around a 42px button on the
   first attempt). A wrapped value still fits: Work auth's two lines measure 40px,
   under the 44px floor. `align-self: stretch` was the other candidate and was
   rejected - it needs `display: flex` on the value, and a flex value loses the
   `text-overflow: ellipsis` that keeps a long email readable. */
.e8-rrail.is-sheet .e8-cand-rail-pane .e8-rrail-fact { align-items: center; min-height: 44px; padding: 0; }
.e8-rrail.is-sheet .e8-cand-rail-pane button.e8-rrail-fact-v.is-edit { padding: 11px 0; }
.e8-rrail:not(.is-sheet) .e8-cand-rail-pane .e8-rrail-fact { align-items: center; min-height: 32px; padding: 0; }
.e8-rrail:not(.is-sheet) .e8-cand-rail-pane button.e8-rrail-fact-v.is-edit { padding: 5px 0; }

/* ---- Piece 10: ONE block grammar ----------------------------------------- */

/* Three row chassis in four adjacent blocks, measured on #/candidate/c-okafor:
     Notes    .e8-cand-rail-noterow  borderless, hairline separator
     Tasks    .e8-rtk-row            borderless, no separator
     Pipeline .e8-applrow            1px bordered card, 6px gap, own hover
     Files    .e8-applrow            same card
   Two of them are cards inside a white rail, which is a card inside a card, and
   they cost 1px border + 6px gutter per row for nothing. One grammar now: a flush
   quiet row, hairline-separated from its neighbour, that fills on hover. */
.e8-cand-rail-pane .e8-applrow,
.e8-cand-rail-pane .e8-rtk-row,
.e8-cand-rail-pane .e8-cand-rail-noterow {
  border: 0;
  background: transparent;
  border-radius: var(--ui-radius);
  margin: 0;
  padding: 8px 0;
  transition: background-color 100ms ease-out;
}
.e8-cand-rail-pane .e8-applrow + .e8-applrow,
.e8-cand-rail-pane .e8-rtk-row + .e8-rtk-row {
  border-top: 1px solid color-mix(in srgb, var(--ui-border) 62%, transparent);
}
.e8-cand-rail-pane .e8-applrow:hover,
.e8-cand-rail-pane .e8-rtk-row:hover,
.e8-cand-rail-pane .e8-cand-rail-noterow:hover {
  background: var(--ui-fill-hover);
}
/* .e8-applrow's own :hover sets border-color, which now has no border to colour;
   the fill above replaces it. .e8-rtk-row is a div, so it gets the same visible
   focus treatment its siblings get from the global :focus-visible rule. */
.e8-cand-rail-pane .e8-cand-rail-noterow:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
/* The separator modifier the note rows used to draw themselves is now the group's
   job, so the two do not double up into a 2px line. */
.e8-cand-rail-pane .e8-cand-rail-noterow.is-sep { border-top: 1px solid color-mix(in srgb, var(--ui-border) 62%, transparent); }

/* MID-WORD TRUNCATION. A 300px rail ellipsised "Technical Project Manager" to
   "Technical Project …" and "Okafor_Daniel_Stand8_Coverletter.pdf" to
   "Okafor_Daniel_Stand8_C…" - the filename case is unreadable, because the part
   that distinguishes two files is the tail. Two lines, and filenames may break
   inside a run since they have no spaces to break at. */
.e8-cand-rail-pane .e8-cand-rail-rowtitle,
.e8-cand-rail-pane .e8-cand-rail-notetitle,
.e8-cand-rail-pane .e8-cand-rail-filename {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  white-space: normal; overflow: hidden;
}
.e8-cand-rail-pane .e8-cand-rail-filename { overflow-wrap: anywhere; }

/* EVERY EMPTY, ONE SHAPE: a line that says what the block is for, then the
   block's own "+ Add …" row directly beneath it. Was four different flavours -
   three bare tertiary sentences with no exit (Notes, Tasks, Pipeline) and one
   sentence + add row (Files). `.e8-rtk-empty` is RecordTasksRail's own markup and
   its WORDS still come from app/screens-tasks.jsx; only its type is normalised
   here, so Tasks reads like its neighbours even though the copy is not ours. */
/* SECONDARY, not tertiary - the same move `.e8-cand-overview-actempty` makes, for the
   same reason. This file had already unified the empty SHAPE across four blocks; the
   ink it unified on disagreed with cand-record.css's, so the record still had two
   voices for "nothing here yet". Tertiary is rgb(156,156,166) = 2.72:1 on the rail's
   white, under the 4.5:1 floor, and an empty state is the one place a block explains
   itself. */
.e8-cand-rail-pane .e8-cand-rail-empty,
.e8-cand-rail-pane .e8-rtk-empty {
  font-size: var(--ui-text-sm); line-height: 1.45; color: var(--ui-text-secondary);
  padding: 2px 0 4px; margin: 0;
}
/* The add row is the invitation, so it stops being a whisper when it is the only
   thing in the block. */
.e8-cand-rail-pane .e8-rrail-addrow { min-height: 32px; }
/* Tasks nests its empty INSIDE .e8-rtk-list and puts the add row after the list,
   so the adjacency has to be taken on the list - `.e8-rtk-empty + .e8-rrail-addrow`
   matches nothing there. Verified by reading screens-tasks.jsx:374-377, not assumed. */
.e8-cand-rail-pane .e8-cand-rail-empty + .e8-rrail-addrow,
.e8-cand-rail-pane .e8-rtk-list:has(.e8-rtk-empty) + .e8-rrail-addrow { color: var(--ui-daybreak-text); }
.e8-cand-rail-pane .e8-cand-rail-empty + .e8-rrail-addrow:hover,
.e8-cand-rail-pane .e8-rtk-list:has(.e8-rtk-empty) + .e8-rrail-addrow:hover { color: var(--ui-accent); }

/* ---- Activity pane ------------------------------------------------------- */
/* was style={{ paddingTop: 12 }} */
.e8-cand-rail-actbody { padding-top: 12px; }

/* ============================================================================
   THE ONE SIZE QUERY - two columns when the pane is wide.

   LAST IN THE FILE ON PURPOSE. A conditional at-rule adds no specificity, so it
   only ever narrows WHEN a declaration applies; written above an unconditional
   rule for the same selector and property it is dead in every state, which has
   shipped here three times. Everything it overrides - `.e8-rrail-sec +
   .e8-rrail-sec` (app.css:2363) and every rule above - is now earlier.

   WHERE IT FIRES. `.e8-cand-rail-pane` is 248-480px as the inline desktop rail
   (RecordRail clamps to that range) and the full width of the bottom sheet on
   touch: ~350px at 390, 794px at 834. So 560px is a threshold only the sheet can
   cross, and it crosses it at roughly a 580px viewport - above the phone, below
   nothing. Desktop is untouched at every rail width the grip can reach; 390 is
   untouched because 350 < 560.

   WHY 560 AND NOT THE 640 THE REVIEW SUGGESTED. Below the threshold the pane is
   one column, and a one-column row is exactly as wide as the pane - so the
   threshold IS the widest a label..value pair can ever get. 560 keeps that at the
   560px the old cap allowed; 640 would have let the single column grow 80px past
   the width the cap was written to prevent before the second column arrived.

   28px gutter, not a border: two rules of vertical hairline inside a 794px sheet
   would draw the sections as a table. align-items:start keeps a short Files
   section from stretching to a tall Pipeline beside it. */
/* THE BAND THIS NOW FIRES IN HAS MOVED, and the honest number is smaller than the
   one this comment shipped with. The tablet drawer at the foot of the file takes
   the sheet to a 360px right panel at any viewport >= 640, so the pane can no
   longer reach 794px at 834 - it is ~340px there and one column, which is the
   point of a rail. What is left is the band where the sheet is still a full-width
   bottom sheet AND wide enough to pair: viewports ~570-639. Narrow, real, and not
   worth deleting a working rule over; the 834 measurements below describe the
   chassis that used to render there, not the one that does now. */
@container e8candrail (min-width: 560px) {
  .e8-cand-rail-cols {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 28px; align-items: start;
  }
  /* The section separator is a SEQUENCE rule (`+`), and a sequence is not a row: in
     two columns it would put a line above the top-right section and none above the
     top-left one. Re-stated positionally instead - items 3+ are the ones with a
     section above them - so each grid ROW carries the line across both columns. */
  .e8-cand-rail-cols > .e8-rrail-sec { border-top: 0; margin-top: 0; }
  .e8-cand-rail-cols > .e8-rrail-sec:nth-child(n + 3) {
    border-top: 1px solid var(--ui-border); margin-top: 6px;
  }
}

/* ============================================================================
   THE SHEET CHASSIS — the rail's mobile/tablet housing.

   WHY THESE RULES ARE HERE AND WHAT BELONGS UPSTREAM. `BottomSheet`
   (shell.jsx:2170) opens at a HALF detent by design: it keeps a 92dvh panel and
   translates it down by 42% of the window, so the panel's own box is unchanged and
   its `flex: 1` body — `overflow-y: auto` and all — is simply pushed off the
   bottom of the screen. A box that is not overflowing has nothing to scroll, so
   at 390x844 the sheet MEASURED transform translateY(354), body scrollHeight 707
   === clientHeight 707, and 60 of 110 rendered elements below the fold with no
   control that reaches them. That is a defect in the shared sheet, not in this
   rail, and the one-line fix belongs in the shared file: RecordBody
   (record-shell.jsx:71) should pass `initial="full"` — BottomSheet already accepts
   it (screens-core.jsx:821 uses it) — and BottomSheet should park a CLOSED sheet
   with the percentage its own base rule already uses instead of writing a resolved
   pixel offset into the inline style, which is what leaves a dead white panel
   painted 199px up the page after a viewport-height change. THIS FILE CANNOT EDIT
   EITHER. What follows makes the candidate rail correct at the widths it was
   filed at; it does nothing for the conversation sheet or the quick-candidate
   sheet, which have the same bug and are not ours to fix.

   `!important` IS LOAD-BEARING AND IS THE MINIMUM. The offset is an INLINE style
   (`style={{ transform: 'translateY(' + curY + 'px)' }}`), so no selector of any
   specificity beats it. `:not(.dragging)` is what keeps the gesture: while a
   finger is down BottomSheet adds `.dragging`, these rules stand aside, and the
   inline transform tracks the drag exactly as before. On release the class comes
   off and the sheet snaps to the detent below. Drag-to-dismiss is untouched — it
   is decided in JS from `dragY`, which still updates.
   ============================================================================ */

/* OPEN -> full detent USED TO BE FORCED HERE, and is not any more. The real cause was that
   RecordBody never passed BottomSheet's `initial` prop, so every record rail opened at the half
   detent (shell.jsx: detentY(d) => d === 'full' ? 0 : H() * 0.42). That is now fixed at source in
   app/record-shell.jsx, which fixes it for every record type rather than for candidates alone.
   Removed rather than left in place: two mechanisms forcing one outcome is how a surface ends up
   with a rule nobody dares delete because nobody can tell which one is load-bearing.
   The CLOSED rule below is NOT redundant with it and stays — it fixes a different bug. */
/* CLOSED -> parked by PERCENTAGE, which cannot go stale. The inline value is
   computed from the window height at the moment it was written, so a sheet parked
   at a 844-tall viewport and then measured at 1112 sits translateY(824) inside the
   viewport - 199px of inert white over the page bottom, aria-hidden and inert so
   it cannot even be clicked away. A percentage is re-resolved against the panel's
   own height on every layout, so there is no stale state to leave behind. */
.e8-bsheet-backdrop:not(.is-open) + .e8-bsheet:not(.dragging):has(.e8-cand-rail-pane) {
  transform: translateY(102%) !important;
}
/* The scrim's opacity is computed from the detent the sheet THINKS it is at
   (0.325 at half), so forcing the panel full without this leaves a full-height
   sheet over a barely-dimmed page. Same `.dragging` stand-aside: the ramp during
   a drag is the feedback that the gesture is working. */
.e8-bsheet-backdrop.is-open:has(+ .e8-bsheet:not(.dragging) .e8-cand-rail-pane) {
  opacity: 1 !important;
}

/* THE RAIL BODY FILLS THE SHEET AND KEEPS THE SCROLL. `.e8-rrail-sheet
   .e8-rrail-body` (app.css:2360) caps the rail at 62dvh = 523px, which was
   invisible while the panel was pushed off-screen and becomes 184px of dead white
   at the bottom of a full sheet. Lifting that cap alone was the first fix and it
   was WRONG in a way only the screen showed: with no bounded height the rail's
   1073px of content overflowed `.e8-bsheet-body` instead, which made the SHEET the
   scroller and carried `.e8-rrail-tabs` out of the viewport with it - so Activity
   became unreachable until you scrolled back up. The tabs are the rail's own
   chrome and must stay put. Bounding the chain instead (sheet body -> sheet
   wrapper -> aside, each a flex column that may shrink) gives `.e8-rrail-body` a
   definite height to be `flex: 1` of, so it scrolls exactly as it does on the
   desktop rail and the tabs stay pinned above it. `min-height: 0` on every link
   because a flex item's default `min-height: auto` refuses to shrink below its
   content and would silently hand the scroll back to the sheet. */
.e8-bsheet-body:has(.e8-cand-rail-pane) { display: flex; flex-direction: column; min-height: 0; }
.e8-bsheet:has(.e8-cand-rail-pane) .e8-rrail-sheet {
  flex: 1; min-height: 0; display: flex; flex-direction: column;
}
.e8-bsheet:has(.e8-cand-rail-pane) .e8-rrail { flex: 1; min-height: 0; }
.e8-bsheet:has(.e8-cand-rail-pane) .e8-rrail-body { max-height: none; }

/* TOUCH TARGETS INSIDE THE SHEET. Measured at 390x844 before: close 40x40, the
   Details/Activity tabs 42x33 and 44x33, the grab handle 38x5. The handle keeps
   its 38x5 LOOK - it is a visual affordance, not a button - and gains a 44px hit
   box around it, because it is the control a thumb reaches for to move the sheet. */
.e8-bsheet:has(.e8-cand-rail-pane) .e8-bsheet-close { width: 44px; height: 44px; }
.e8-bsheet:has(.e8-cand-rail-pane) .e8-rrail-tab {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 0 2px;
}
/* The handle grows to 44px WITHOUT moving anything: the extra 29px is taken back
   as negative bottom margin, so the box overlaps the header that follows it and the
   sheet's own top rhythm is unchanged. That is not a trick to satisfy a ruler - the
   header carries the SAME touch handlers as the handle (shell.jsx:2223 spreads the
   identical `bind`), so every pixel of the overlap drags the sheet either way; what
   changes is that the element a test measures is finally as big as the gesture it
   offers. Two things make the overlap safe. The pill is painted as a BACKGROUND
   rather than a pseudo-element so the handle needs no `position`, and a
   non-positioned earlier sibling paints - and hit-tests - BELOW the header; with
   `position: relative` it would have painted above and swallowed the top half of the
   44px close button. And `pointer-events` is left alone: the header wins the
   overlap, which is what keeps Close clickable (verified with elementFromPoint at
   the close button's centre). */
.e8-bsheet:has(.e8-cand-rail-pane) .e8-bsheet-grab {
  width: 100%; height: 44px; margin: 0 0 -29px;
  background: linear-gradient(var(--ui-border-strong), var(--ui-border-strong))
    center 8px / 38px 5px no-repeat;
}

/* THE SCROLL IS NOW VISIBLE. Both chassis scroll more content than they show -
   the desktop rail measured scrollHeight 1051 against clientHeight 809 at
   1440x900, with PIPELINE half-clipped and FILES entirely below the fold - and
   macOS overlay scrollbars paint nothing until the wheel moves, so "there is more
   below" was carried by nothing at all. Two background layers do it without a
   node: a `local` cover that travels with the content and an underlying `scroll`
   shadow that the cover hides once the end is reached. Bottom only; the rail is
   read top-down and a top shadow would sit under the section head permanently.

   ONLY THE ELEMENT THAT ACTUALLY SCROLLS CAN CARRY THIS. A `local`/`scroll` pair
   is meaningless on anything else, and it is also invisible if a descendant paints
   over it - `.e8-rrail` fills `var(--ui-surface)` (app.css:2340), so an earlier
   version of this rule that sat on `.e8-bsheet-body` was drawn and then covered by
   the rail. Proven rather than reasoned: with the shadow forced to opaque red it
   did not appear until the scroller and the painter were the same element. That is
   now `.e8-rrail-body` in BOTH chassis, which is why one selector serves both. */
.e8-rrail-body:has(> .e8-cand-rail-pane) {
  background:
    linear-gradient(to top, var(--ui-surface), transparent) bottom / 100% 24px no-repeat local,
    radial-gradient(farthest-side at 50% 100%, color-mix(in srgb, var(--ui-text) 16%, transparent), transparent)
      bottom / 100% 8px no-repeat scroll;
}

/* ============================================================================
   TABLET AND UP — the sheet becomes the RAIL it is standing in for.

   LAST IN THE FILE, after every unconditional rule it narrows, because a
   conditional at-rule adds no specificity and one written above its own base rule
   is dead in every state (CLAUDE.md; it has shipped here three times).

   A @media AND NOT A @container, deliberately. The record's container rule exists
   because the resizable rail squeezes the content column without the viewport
   moving - but this sheet is `position: fixed`, a SIBLING of `.e8-recordbody` and
   outside the `e8rec` container entirely (the pane's own `e8candrail` container
   measures the pane, which is what this block is about to change). Its geometry is
   a pure function of the viewport, so the viewport is the honest thing to ask.
   640 is one of the two canonical breakpoints in this repo.

   WHY. At 834x1112 the rail opened as a 669px-tall bottom sheet covering 60% of
   the record - the phone disclosure idiom on a tablet, where §2.7 frames the rail
   as a rail: "collapsed to a toggle by default. Not an open sheet." Collapsed-by-
   default was already right; this is the other half. 360px is the top of §2.7's
   280-360 desktop range and it overlays rather than pushing the content column, so
   the record underneath does not reflow when the rail is summoned.

   BOTH TRANSFORM SELECTORS ARE RESTATED, and the `.dragging` one is added rather
   than excluded. Equal specificity to the block above plus later position is what
   makes translateX win over translateY; and a drawer that slid vertically under a
   downward drag would be nonsense, so here the panel holds still while the gesture
   runs and BottomSheet still closes it on release if the fling qualifies. The grab
   handle is hidden with it - it is a bottom-sheet affordance and it would be
   lying about which axis moves. The close button remains, at 44px. */
@media (min-width: 640px) {
  .e8-bsheet:has(.e8-cand-rail-pane) {
    left: auto; right: 0; top: 0; bottom: 0;
    width: min(360px, 92vw); height: auto;
    border-radius: 0;
    border-left: 1px solid var(--ui-border);
    box-shadow: var(--ui-shadow-overlay);
  }
  .e8-bsheet:has(.e8-cand-rail-pane) .e8-bsheet-grab { display: none; }
  .e8-bsheet-backdrop.is-open + .e8-bsheet:not(.dragging):has(.e8-cand-rail-pane),
  .e8-bsheet-backdrop.is-open + .e8-bsheet.dragging:has(.e8-cand-rail-pane) {
    transform: translateX(0) !important;
  }
  .e8-bsheet-backdrop:not(.is-open) + .e8-bsheet:not(.dragging):has(.e8-cand-rail-pane),
  .e8-bsheet-backdrop:not(.is-open) + .e8-bsheet.dragging:has(.e8-cand-rail-pane) {
    transform: translateX(102%) !important;
  }
}
