/* ═══════════════════════════════════════════════════════════════════════════
   THE RAIL AND THE PANE — the house pattern for every list-and-detail screen
   ═══════════════════════════════════════════════════════════════════════════

   THE RULE (owner, 2026-08-27): when a screen is a LIST on the left and ONE
   RECORD on the right, it is built this way. Not something like it, and not a
   second spelling of it — this one. Every such screen in the app is the same
   screen with different rows in it, and a reader who has learnt one has learnt
   all of them.

   What that means, concretely, and all four are load-bearing:

     1. TWO FRAMES, NOT ONE PAGE. `#rail` carries the list and `#pane` carries
        the record. They reload independently, which is the entire reason for
        the split: typing in the search narrows the list without disturbing the
        record open beside it, and picking a record does not rebuild the list.

     2. THE RAIL LAZY-LOADS. It is a scroll, never a paginator — a numbered
        pager in a 272px column is clutter, and a thousand rows handed over at
        once is a stalled page. The server stamps the next batch's URL on a
        sentinel at the end of the current batch; rail_controller.js follows it
        and appends. The server owns the paging; the browser owns nothing but
        the scroll position.

     3. THE RAIL IS SEARCHABLE, and on whatever people actually quote — a name,
        a number, a code. The box sits INSIDE the rail frame and submits to
        wherever the reader already is, with `turbo-action="replace"` so eight
        letters do not leave eight entries in the back button.

     4. THE PANE ADVANCES. Picking a record is a real step: it is linkable, it
        survives a refresh, and the back button undoes it. That frame carries
        `turbo-action="advance"`; the rail's search never does.

   The rail is sticky and owns its own scroll; the pane scrolls with the page.
   That is what lets a reader walk the list with the arrow keys without
   dragging the record up and down behind them.

   Nothing in here spells a colour that is not a variable from layout.css, and
   every selector sits under .railed, .rail or .pane so the short names cannot
   leak onto anybody else's page.

   Used by: the staff directory (Admin › Central), the customer register (four
   sections), and the customer browser (Finance and Sales). Adding a fifth is a
   controller that includes Railed and a row partial — never new CSS.
   ═══════════════════════════════════════════════════════════════════════════ */

.railed { display: flex; gap: 1.4rem; align-items: flex-start; }

/* ── left: the search, and the roll under it ── */
.rail {
  flex: 0 0 272px; display: flex; flex-direction: column; gap: 0.5rem;
  position: sticky; top: 1rem; max-height: calc(100vh - 2rem);
}
.rail__search { flex: 0 0 auto; }

/* Clear rides INSIDE the box it clears — a search field's own X, not a word
   standing beside it competing for the eye. It is still a real link to the
   unfiltered page with JavaScript off. */
.rail__searchbox { position: relative; }
.rail__searchbox input { width: 100%; padding-right: 1.9rem; }
.rail__clear {
  position: absolute; top: 50%; right: 0.4rem; transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.15rem; height: 1.15rem; border-radius: 50%;
  color: var(--ink-4); text-decoration: none; font-size: 0.62rem; line-height: 1;
}
.rail__clear:hover { color: var(--ink); background: var(--well); }
.rail__clear[hidden] { display: none; }

/* A word on a row that needs somebody's attention — an arrival nobody has
   seated, a record nobody has finished. Amber earns the eye without the alarm
   of red: nothing is wrong, something is unfinished. */
.rail__flag {
  flex: 0 0 auto; padding: 0.05rem 0.4rem; border-radius: 999px;
  font-size: 0.62rem; font-weight: 700; letter-spacing: 0.03em;
  text-transform: uppercase; color: #8a5a00; background: #fdf0d5;
  border: 1px solid #f0dcae;
}
.rail__item.on .rail__flag { background: var(--deck); border-color: var(--deck); }
.rail__meta {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  margin-top: 0.25rem; min-height: 1.6rem;
}
.rail__n {
  font-family: var(--bs-font-monospace); font-size: 0.66rem; color: var(--ink-4);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rail__meta .btn { padding: 0 0.25rem; font-size: 0.72rem; }

.rail__list {
  flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: 0.15rem; padding-right: 0.15rem;
}

/* said when the roll starts part-way down, because it opened on somebody */
.rail__earlier {
  flex: 0 0 auto; display: block; text-align: center; text-decoration: none;
  font-family: var(--bs-font-monospace); font-size: 0.64rem; color: var(--ink-4);
  padding: 0.25rem; border-radius: 6px; border: 1px dashed var(--edge-2);
}
.rail__earlier:hover { color: var(--green-ink); border-color: var(--green); background: var(--rail); }

.rail__item {
  display: flex; flex-direction: column; gap: 0.05rem;
  text-decoration: none; padding: 0.4rem 0.6rem; border-radius: 8px;
  border: 1px solid transparent; color: var(--ink-3); flex: 0 0 auto;
  /* the arrows scroll the roll, and a row must not land half under the edge */
  scroll-margin: 0.3rem;
}
.rail__item:hover { background: var(--rail); color: var(--ink); }
.rail__item.on { background: var(--green); border-color: var(--green-ink); color: var(--deck); }
.rail__name {
  flex: 1 1 auto; min-width: 0;
  font-size: 0.83rem; line-height: 1.25; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* A row whose row is a name and a flag rather than a name and a sub-line. */
.rail__line { display: flex; align-items: center; gap: 0.4rem; min-width: 0; }

/* struck through rather than hidden: a switched-off record is written
   differently, not filtered away behind a select */
.rail__item.is-off .rail__name { color: var(--ink-4); text-decoration: line-through; }
.rail__item.on .rail__name { color: var(--deck); }
.rail__sub {
  font-family: var(--bs-font-monospace); font-size: 0.62rem; color: var(--ink-4);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rail__item.on .rail__sub { color: var(--deck); opacity: 0.85; }
.rail__dot { opacity: 0.5; margin: 0 0.15rem; }

.rail__more, .rail__none {
  font-family: var(--bs-font-monospace); font-size: 0.68rem; color: var(--ink-4);
  padding: 0.7rem 0.6rem; text-align: center; margin: 0;
}
.rail__none { text-align: left; }

/* ── right: whatever is open about the customer ── */
.pane { flex: 1 1 auto; min-width: 0; }

.pane__head {
  display: flex; align-items: flex-start; gap: 1rem; flex-wrap: wrap;
  border-bottom: 1px solid var(--edge); padding-bottom: 0.7rem; margin-bottom: 1rem;
}
.pane__heading { min-width: 0; }
.pane__title { font-size: 1.35rem; font-weight: 700; color: var(--ink); margin: 0; line-height: 1.2; }
.pane__sub { font-size: 0.8rem; color: var(--ink-3); margin: 0.2rem 0 0; }
.pane__actions {
  margin-left: auto; flex: 0 0 auto;
  display: flex; align-items: center; gap: 0.6rem;
}

/* the invitation, while nobody is picked */
.pane__none {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 0.35rem; min-height: 46vh; color: var(--ink-4);
  border: 1px dashed var(--edge-2); border-radius: 14px; padding: 2rem;
}
.pane__none i { font-size: 2rem; opacity: 0.5; }
.pane__none-lead { font-size: 1.05rem; color: var(--ink-3); margin: 0; }
.pane__none-hint { font-size: 0.8rem; margin: 0; }

/* The profile's two stacks. Not Bootstrap's grid: its breakpoints read the
   VIEWPORT, and the width that decides this is the pane's — the rail has
   already taken 272px of it. */
.pane__cards { display: grid; grid-template-columns: 1fr; gap: 0 1.25rem; align-items: start; }
@media (min-width: 1200px) {
  .pane__cards { grid-template-columns: minmax(250px, 0.85fr) minmax(340px, 2fr); }
}

/* ── the narrow screen: one column, roll first ──
   The rail stops being a column and becomes the top of the page, capped so the
   pane under it is still reachable without scrolling past 936 names. Picking
   somebody scrolls them the pane; that is the honest simple thing, and it is
   what the masters browser does with its books. */
@media (max-width: 900px) {
  .railed { flex-direction: column; }
  .rail { flex-basis: auto; width: 100%; position: static; max-height: none; }
  .rail__list { max-height: 42vh; }
  .pane { width: 100%; }
}

/* An invoice printed from the pane is a document handed across a counter; the
   roll beside it is furniture. (The rest of the print rules are in invoice.css,
   which is where the sheet itself is styled.) */
@media print {
  .rail, .pane__head { display: none !important; }
  .railed { display: block; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   THE PANE'S OWN VOCABULARY
   ═══════════════════════════════════════════════════════════════════════════

   What a record looks like once it is open. Tabs when one record is several
   sections' business, a field grid when it is a form, and a row list when it
   is a set of small things granted or taken away one at a time.

   Everything here is scoped to .pane, so a page can use the rail without the
   tabs, or the tabs without the rail, and neither knows about the other. */

/* ── the heading, and whatever acts on the record ── */
.pane__flag {
  margin-left: 0.35rem;
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.03em; color: var(--ink-4);
}

/* ── tabs: one record, several sections' business ──
   LINKS, never client state. A save reloads the pane, and a tab remembered
   only in JavaScript would be forgotten exactly then — so which tab is
   showing rides in the URL. */
.pane__tabs {
  display: flex; gap: 0.15rem; flex-wrap: wrap;
  border-bottom: 1px solid var(--edge); margin-bottom: 1rem;
}
.pane__tab {
  padding: 0.4rem 0.75rem; font-size: 0.85rem; font-weight: 600;
  color: var(--ink-3); text-decoration: none;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.pane__tab:hover { color: var(--ink); background: var(--rail); }
.pane__tab.on {
  color: var(--ink);
  border-bottom-color: var(--section-color, var(--green-ink));
}

/* ── what just happened ──
   A receipt and a refusal must not be mistaken for one another at a glance,
   and they do not sit in the same place either.

   THE RECEIPT COSTS NO HEIGHT. It rides in the head beside whatever acts on the
   record, where the row is already as tall as the name — so a save does not
   push the form down and the next click does not pull it back up. It cannot
   wrap, which is what guarantees that: a receipt is a handful of words, and one
   that ever grew long enough to need two lines would be a message, not a
   receipt. */
.pane__receipt {
  flex: 0 0 auto; white-space: nowrap;
  font-size: 0.8rem; color: var(--green-ink);
}

/* A refusal keeps its place in the flow. Something that went wrong is worth
   the jolt, and worth reading whole rather than ellipsed into a corner. */
.pane__warned { margin: 0 0 0.6rem; font-size: 0.82rem; color: #8a3a1d; }

/* ── a form in the pane ──
   TWO COLUMNS, fixed, or one when there is no room for two — never "as many as
   will fit". An auto-fitting grid re-flows as the pane changes width, so the
   same form is a different shape on two screens and a reader who has learnt
   where the mobile is has learnt it for one window size only.

   The width that decides is the PANE's, not the viewport's — the rail has
   already taken 272px of it — so the breakpoint is set where the pane itself
   stops having room, not where the screen does. */
.pane__fields {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 1rem; margin-bottom: 0.75rem;
}
.pane__fields > div { margin-bottom: 0.75rem; min-width: 0; }

@media (max-width: 1100px) {
  .pane__fields { grid-template-columns: minmax(0, 1fr); }
}

/* Out of the grid: a paragraph in a half-width box is a column of six words,
   and nobody writes a note that way twice. */
.pane__wide { margin-bottom: 0.75rem; }

/* a read-only field states its value where the input would sit, so a reader
   whose section cannot write meets the same layout as one who can */
.pane__value { margin: 0; padding: 0.35rem 0; font-size: 0.9rem; color: var(--ink); }

/* ── a list of small things inside the pane ──
   Section levels, ledgers, logins, executives: a name on the left and whatever
   acts on it hard right. */
.pane__rows { border: 1px solid var(--edge); border-radius: 0.5rem; overflow: hidden; }
.pane__row {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.4rem 0.75rem; border-bottom: 1px solid var(--edge);
}
.pane__row:nth-child(odd) { background: var(--rail); }
.pane__row:last-child { border-bottom: 0; }
.pane__rowname { flex: 1 1 auto; min-width: 0; font-size: 0.85rem; color: var(--ink); }
.pane__row select { flex: 0 0 auto; width: 10rem; }
.pane__row .btn { flex: 0 0 auto; }

/* struck through rather than badged: a thing switched off is written
   differently, not labelled */
.pane__off {
  margin-left: 0.35rem; font-size: 0.7rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.03em; color: var(--ink-4);
}

/* ── adding one ──
   Under the rows it adds to, one line, no card. */
.pane__add { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.85rem; flex-wrap: wrap; }
.pane__add .form-control, .pane__add .form-select { max-width: 16rem; }

/* ── a switch that overrules the rows below it ──
   Set apart, and above them, because it answers a different question than
   "how much of this". */
.pane__switch {
  padding: 0.55rem 0.75rem; margin-bottom: 0.75rem;
  border: 1px solid var(--edge); border-radius: 0.5rem; background: var(--rail);
}
.pane__switch .form-check-label { font-weight: 600; }

/* while the switch is on, the rows below state a fallback rather than a fact —
   dimmed to say so, and left entirely usable, because that fallback is still
   worth setting */
.pane__rows.is-overruled { opacity: 0.55; }

/* ── a group under the rows, added rather than overruling ── */
.pane__group { margin-top: 0.9rem; padding-top: 0.75rem; border-top: 1px solid var(--edge); }


/* Whether the account opens anything, said in the heading where it costs no
   height — the row is already as tall as the name. */
.pane__state {
  display: inline-block; margin-right: 0.3rem;
  padding: 0.05rem 0.4rem; border-radius: 999px;
  font-size: 0.62rem; font-weight: 700; letter-spacing: 0.03em;
  text-transform: none; color: var(--ink-4);
  background: var(--well); border: 1px solid var(--edge-2);
}
.pane__state.is-provisioned { color: var(--green-ink); background: #e4efe2; border-color: #c3ddc0; }
.pane__state.is-unprovisioned { color: #8a5a00; background: #fdf0d5; border-color: #f0dcae; }

/* ── a fact, where a field would sit ──
   A disabled input reads as a field somebody broke. This reads as what it is:
   something true about the account that this screen does not decide. */
/* It sits where an input would sit and stands as tall as one, so the two
   columns line up — but it is prose, not a control, and reads as prose. */
.pane__stated {
  display: flex; align-items: center; gap: 0.5rem;
  margin: 0; padding: 0.375rem 0.75rem; min-height: calc(1.5em + 0.75rem + 2px);
  border: 1px dashed var(--edge-2); border-radius: var(--bs-border-radius, 0.375rem);
}
.pane__stated-value {
  flex: 1 1 auto; min-width: 0;
  font-family: var(--bs-font-monospace); font-size: 0.85rem; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pane__stated-mark {
  flex: 0 0 auto; font-size: 0.66rem; color: var(--ink-4);
  padding: 0.05rem 0.4rem; border-radius: 999px;
  background: var(--well); border: 1px solid var(--edge-2);
}
