/* ===========================================================================
   SHELL — the four-column frame.

   OPTIONAL. Everything in tokens/base/components works without this file. Load
   it when you are building an operator console, an admin app or a dashboard —
   anything where a user learns one screen and then navigates all of them.

   ┌──────┬────────────┬─────────────────────────────┬──────────────┐
   │      │            │  header                     │              │
   │      │            ├─────────────────────────────┤              │
   │ rail │  sub-nav   │  content pane               │  side pane   │
   │      │            │                             │              │
   └──────┴────────────┴─────────────────────────────┴──────────────┘
      ↑         ↑                    ↑                       ↑
    which    which slice        the slice            the one thing
    screen     of it                                   selected

   THE RULE THIS FILE EXISTS FOR: screens differ in what they put in the four
   columns, never in whether they have them. That is what lets a reader who has
   learned one screen navigate all of them, and it is why one set of names
   covers the entire app.

   THE SECOND RULE: the shell's tokens belong to the shell. The rail and the
   sub-nav hold no content, only the way to it. `--hairline` and `--shell-ink`
   belong to these two columns and nothing else: a component outside them
   reaching for either is claiming to be part of the shell. Their surfaces
   carry less chroma than the content's, which keeps their grey text grey
   whatever ground is picked, but the columns are told apart by lightness and
   the hairline, never by colour (tokens.css, shell surfaces, says why).

   LEVELS. A screen does not navigate away when you open something. It replaces
   its content pane with a level.

     level 1   the list. Above it, `.pane-bar` — a caption saying what this
               slice is and how many are in it.
     level 2   one thing from that list, opened. `.level-deep`.
     level 3   one thing from inside level 2.

   Say "the settings screen, level 2". *Page* is the word to avoid: nothing
   here is a page in the browser sense — the app never loads a second document.
   =========================================================================== */

.shell {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  /* The rail is a fixed column because its contents are fixed: the screens,
     and the two controls under them. Everything that grows lives in the page
     beside it. Narrow, because it holds a glyph and one word — the column that
     holds this screen's own filters is the next one along, and it belongs to
     the screen rather than to the shell. */
  grid-template-columns: 68px minmax(0, 1fr);
}

.shell-page {
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}


/* ------------------------------------------------------------------ rail -- */

.rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: var(--space-3) 0;
  background: var(--surface-rail);
  border-right: 1px solid var(--hairline);
  overflow-y: auto;
  overflow-x: hidden;
}

/* The mark alone. At 68px there is no width for a sentence, so the app's name
   lives in the tab title, on the sign-in page and in the side pane — three
   places that can hold it whole. The `title` carries it here. */
.rail-brand {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  margin-bottom: 13px;
  border-radius: var(--radius);
  background: var(--primary-cont);
  color: var(--on-primary-cont);
}

/* The app's one add action, under the brand block and above the screens, so it
   is in the same place on every screen. Violet because it is the action; a
   square because everything else at the top of the rail is. Icon only, so the
   button carries an `aria-label` and a `title`. */
.rail-add {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
  margin-bottom: var(--space-3);
  border: 0;
  border-radius: var(--radius);
  background: var(--primary);
  color: var(--on-primary);
  cursor: pointer;
  transition: background var(--motion-short) var(--easing),
              scale var(--motion-short) var(--easing);
}
.rail-add:hover { background: color-mix(in srgb, var(--primary) 88%, var(--on)); }
.rail-add:active { scale: .94; }
.rail-add:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

.rail-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  width: 58px;
  flex: 0 0 auto;
  padding: 4px 0 2px;
  background: transparent;
  border: 0;
  /* The shell's own grey. `--dim` is a tinted grey and reads as a faint colour
     against a neutral surface rather than as quiet text, which is the whole
     reason the neutral axis carries an ink of its own. */
  color: var(--shell-ink);
  font: inherit;
  font-size: var(--text-2);
  line-height: 1.2;
  text-align: center;
  cursor: pointer;
  transition: color var(--motion-short) var(--easing);
}
.rail-item:hover { color: var(--on); }
.rail-item:hover .rail-pill { background: var(--surface-cont); color: var(--on-variant); }
.rail-item:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: -2px;
  border-radius: var(--radius);
}

/* The active screen is a filled square behind the glyph — not a border, which
   would shift the label. A square, to match the brand block above it; the
   class kept its name from when it was a pill. */
.rail-pill {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius);
  color: var(--shell-ink);
  transition: background var(--motion-short) var(--easing),
              color var(--motion-short) var(--easing);
}
/* A rail item may be a link when each screen is its own page: it keeps the
   item's colour, not a link's. */
a.rail-item { color: inherit; text-decoration: none; }
.rail-item.is-active { color: var(--on); font-weight: 500; }
.rail-item.is-active .rail-pill { background: var(--primary-cont); color: var(--on-primary-cont); }
/* The glyph inside the pill, and the mark inside the brand block. Both are
   icon slots; both are named so the markup reads as what it is. */
.rail-glyph,
.rail-brand-mark { display: grid; place-items: center; }
.rail-label { display: block; white-space: nowrap; }

.rail-foot {
  margin-top: auto;
  padding-top: var(--space-4);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
}
.rail-action {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--surface-cont);
  color: var(--on-variant);
  cursor: pointer;
  transition: background var(--motion-short) var(--easing);
}
.rail-action:hover { background: var(--surface-high); }
.rail-action:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* Who is signed in, as one letter. The whole string is the `title`. */
.rail-avatar {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--surface-est);
  color: var(--on);
  font: inherit;
  font-size: var(--text-5);
  font-weight: 500;
  cursor: pointer;
  text-transform: uppercase;
}
.rail-avatar:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }


/* ---------------------------------------------------------------- header --
   One row, fixed height, on every screen. The left half says where you are and
   how you got there; the right half is the save cluster, which holds its slot
   whether or not there is anything to save — so the place a change is
   committed never moves from screen to screen. */

.topbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: 0 0 56px;
  padding: 0 var(--pane-x) 0 22px;
  border-bottom: 1px solid var(--hairline);
}
.topbar h1 {
  margin: 0;
  flex: 0 0 auto;
  font-size: var(--text-12);
  font-weight: 500;
  letter-spacing: -.015em;
}

/* The trail into a screen. The screen's own name stays in the <h1> on the left
   and the crumbs append to it, so the way back out is always the leftmost
   thing rather than something that moved. */
.crumbs { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.crumbs:empty { display: none; }
.crumb-sep { display: grid; place-items: center; color: var(--fainter); }
.crumb {
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: var(--text-9);
  color: var(--dim);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color var(--motion-short) var(--easing);
}
.crumb:hover { color: var(--on); }
.crumb:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
/* The last crumb is where you are, so it carries the colour that means "this
   is the thing you are looking at" and is not a button. */
.crumb.is-current { color: var(--accent-2); cursor: default; }

/* One quiet line of counted fact: "6 loaded · 447 items". */
.screen-meta {
  flex: 0 1 auto;
  font-family: var(--mono);
  font-size: var(--text-3);
  color: var(--faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.topbar-right {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-left: auto;
  flex: 0 0 auto;
}
.topbar-right .ghost { height: var(--control-h-lg); }


/* ---------------------------------------------------------------- panels -- */

main { flex: 1 1 auto; min-height: 0; display: flex; }

.panel { display: none; flex: 1 1 auto; min-width: 0; }
.panel.is-active { display: flex; }


/* --------------------------------------------------------------- sub-nav --
   The permanent second column. On a list screen it holds that list's filters
   and sorting; drilled into a row it holds that row's sections with the way
   back at its top. It is never a bar above the list, because a bar takes width
   from the thing it filters and moves every time the filters change shape.

   Drilled in, it reads top to bottom: `.subnav-back`, the `.subnav-tab`s of
   the one thing, a `.subnav-sep`, then any filters, as `.subnav-option`s. */

.subnav {
  flex: 0 0 216px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--surface-nav);
  border-right: 1px solid var(--hairline);
}
.subnav:empty { display: none; }

.subnav-head {
  flex: 0 0 auto;
  padding: var(--space-3);
  /* One control's worth, whether or not it holds one. The `1px` is the border
     below: `box-sizing` is border-box everywhere, so a min-height that left it
     out would come up exactly one pixel short of a head that has a control in
     it, and the column under it would sit a pixel high on that one screen. */
  min-height: calc(var(--control-h) + var(--space-3) * 2 + 1px);
  border-bottom: 1px solid var(--hairline);
}
.subnav-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: var(--space-2) 9px var(--space-4);
}
/* Where a SLICE'S ACTIONS live — "Enable shown", "Disable shown". They act on
   whatever this column narrowed to, so they sit under the filters that built
   it, and the count on the group's own label says how many the gesture will
   reach.

   Not on the list's caption bar. The caption bar *describes* what is on
   screen, and an action there reads as "enable this list" instead of "enable
   what I just narrowed to" — different sets the moment a filter is on. */
.subnav-foot {
  flex: 0 0 auto;
  padding: var(--space-2) 9px var(--space-3);
  border-top: 1px solid var(--hairline);
}

.subnav-title {
  padding: 9px var(--control-x) 5px;
  font-size: var(--text-1);
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--shell-ink);
}
.subnav-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  height: var(--control-h);
  padding: 0 var(--control-x);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--shell-ink);
  font: inherit;
  font-size: var(--text-5);
  text-align: left;
  cursor: pointer;
  transition: background var(--motion-short) var(--easing),
              color var(--motion-short) var(--easing);
}
.subnav-item:hover { background: var(--surface-cont); color: var(--on); }
.subnav-item.is-active { background: var(--secondary-cont); color: var(--on-secondary-cont); }
.subnav-item:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.subnav-label { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.subnav-n { flex: 0 0 auto; font-family: var(--mono); font-size: var(--text-2); color: var(--fainter); }
/* On the filled pill the count takes the pill's own ink: `--fainter` is made
   for the column's surface and on the container it fell under 4.5:1. */
.subnav-item.is-active .subnav-n { color: var(--on-secondary-cont); }

/* A SECTION of the one thing this column has drilled into: Performance, Client
   doc, Batches. The way into a view, so it is navigation and speaks like the
   rail: a glyph, a stronger ink than a filter's, and violet when it is the
   one on screen ("this is on"), a step deeper than a filter that is on
   (`--nav-current`) and heavier. A filter only narrows a list, so it stays
   the quieter pill below the divider.

   Drawn in the filters' own colours at first, a column of six pills read as
   one list, and nobody could tell which press moved them to another view and
   which only hid some rows. */
.subnav-tab {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  height: var(--control-h);
  padding: 0 var(--control-x) 0 var(--control-x-sm);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  /* A content ink inside the shell, as the rail's hover is: the shell's own
     grey is for what is quiet here, and a section is not. */
  color: var(--on-variant);
  font: inherit;
  font-size: var(--text-5);
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion-short) var(--easing),
              color var(--motion-short) var(--easing);
}
.subnav-tab-glyph { display: grid; place-items: center; flex: 0 0 auto; color: var(--shell-ink); }
.subnav-tab-glyph .icon { --icon-size: var(--icon-size-sm); }
.subnav-tab:hover { background: var(--surface-cont); color: var(--on); }
.subnav-tab:hover .subnav-tab-glyph { color: inherit; }
.subnav-tab.is-active,
.subnav-tab[aria-current="page"] { background: var(--nav-current); color: var(--on-nav-current); font-weight: 600; }
.subnav-tab.is-active :is(.subnav-tab-glyph, .subnav-n),
.subnav-tab[aria-current="page"] :is(.subnav-tab-glyph, .subnav-n) { color: inherit; }
.subnav-tab:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }

/* Pills in a column take a sliver apart, as the rail's items do. Flush, a
   hovered pill and the active one beside it met in one shape, and read as a
   single wide selection. Costs 2px a pill. */
:is(.subnav-item, .subnav-tab) + :is(.subnav-item, .subnav-tab) { margin-top: 2px; }

/* The way back out of a level, as the first thing in the sub-nav body: an
   arrow and the name of where it goes ("All batches"), never "Back", which
   says nothing about where. Quiet, in the shell's grey, because it is the one
   press here that leaves what you are looking at. */
.subnav-back {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  height: var(--control-h);
  margin-bottom: var(--space-2);
  padding: 0 var(--control-x) 0 var(--control-x-sm);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--shell-ink);
  font: inherit;
  font-size: var(--text-5);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion-short) var(--easing),
              color var(--motion-short) var(--easing);
}
.subnav-back [data-icon] { display: grid; place-items: center; flex: 0 0 auto; }
.subnav-back .icon { --icon-size: var(--icon-size-sm); }
.subnav-back:hover { background: var(--surface-cont); color: var(--on); }
.subnav-back:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }

/* Between the sections above and the filters below: a hairline, because it is
   inside the shell's own column, with the room of a group label round it. It
   may carry a label, which reads like `.subnav-title` and runs into the line;
   empty, it is the line alone, and takes role="separator" (a separator's
   text is not read out, so a labelled one goes without). */
.subnav-sep {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-3) 0 var(--space-2);
  padding: 0 var(--control-x);
  font-size: var(--text-1);
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--shell-ink);
}
.subnav-sep::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--hairline); }


/* A filter drawn as what it is: a radio when the group picks one ("Show: all,
   waiting on you, ready"), a checkbox when it narrows by any of several
   (stages, clients). A label round a real input, so a click anywhere on the
   line, Space and the arrow keys all work, and a screen reader hears "radio,
   2 of 5". Put a group of them in a <fieldset class="subnav-group"> whose
   <legend class="subnav-title"> names it.

   The control carries the state, so the line takes no fill when it is on:
   only a stronger ink. **What it cost:** drawn as pills, filters and the
   sections above them were the same shape, and a press on one could as well
   have moved to another view as hidden some rows. */
.subnav-group { margin: 0; padding: 0; border: 0; min-width: 0; }
.subnav-group > legend { padding: 9px var(--control-x) 5px; }
.subnav-option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: var(--control-h);
  padding: 0 var(--control-x);
  border-radius: var(--radius-sm);
  color: var(--shell-ink);
  font-size: var(--text-5);
  cursor: pointer;
  user-select: none;
  transition: background var(--motion-short) var(--easing),
              color var(--motion-short) var(--easing);
}
.subnav-option:hover { background: var(--surface-cont); color: var(--on); }
.subnav-option:has(input:checked) { color: var(--on); }
.subnav-option:has(input:focus-visible) { outline: 2px solid var(--primary); outline-offset: -2px; }
/* The base rule draws the input's own focus ring; the line's ring says it here. */
.subnav-option input:focus-visible { outline: 0; }
.subnav-option + .subnav-option { margin-top: 0; }
.subnav-option .subnav-n { margin-left: auto; }


/* --------------------------------------------------------- content column --
   One scroll container per level, not one for the screen: a settings level
   keeps its own heading fixed while its fields scroll, and a list level
   scrolls whole. */

.screen-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.level {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: var(--space-4) var(--pane-x) var(--space-5);
}
.level[hidden] { display: none; }

/* A level you drilled into. Drawn on the same surface as the list it replaced:
   the level's own head, the breadcrumb and the sub-nav all say it replaced the
   list, and a tint saying it a fourth time only made two levels of one screen
   look like two different screens.

   It arrives from the right, which is the direction you went. Toggling
   `hidden` restarts the animation, so this needs no script and no class to
   clear — and the global `prefers-reduced-motion` block in base.css already
   turns it into an instant swap for anyone who has asked for that. */
.level-deep {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0;
  animation: level-in var(--motion-long) var(--easing) both;
}
@keyframes level-in {
  from { opacity: 0; transform: translateX(10px); }
  to   { opacity: 1; transform: none; }
}

.level-head {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-start;
  gap: 13px;
  padding: var(--space-4) var(--pane-x) var(--space-3);
  border-bottom: 1px solid var(--rule);
}
.level-mark {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  margin-top: 2px;
  color: var(--accent-2);
}
.level-mark svg { width: var(--icon-size-lg); height: var(--icon-size-lg); }
.level-ident { min-width: 0; flex: 1 1 auto; }
/* Whatever the level carries on its right: a gear, a state chip, the way out.
   Never a bulk action — those belong under the filters that built the slice,
   in `.subnav-foot`. */
.level-head-aside { display: flex; align-items: center; gap: var(--space-2); flex: 0 0 auto; }
.level-title { font-family: var(--font-display); font-size: var(--text-11); font-weight: 500; }
.level-slug {
  font-family: var(--mono);
  font-size: var(--text-3);
  color: var(--faint);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.level-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: var(--pane-y) var(--pane-x) var(--space-5);
}

/* The caption above a level-1 list: a mark, what this slice is called, how
   many are in it, and where it is stored. It is a CAPTION — an action on the
   whole slice lives in `.subnav-foot`, and adding is the list's own last row.
   It belongs to level 1 only: drilled in, `.level-head` is saying the same
   things about one thing. */
.pane-bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-4);
  border-bottom: 1px solid var(--rule);
}
.pane-bar-mark { display: grid; place-items: center; flex: 0 0 auto; color: var(--accent-2); }
.pane-bar-mark svg { width: var(--icon-size-lg); height: var(--icon-size-lg); }
.pane-bar-ident { min-width: 0; flex: 1 1 auto; }
/* One line each, cut where the bar ends. A slice's name is short and the bar
   is the tallest thing on the screen after the header — so a name that wraps
   because the window is narrow takes the whole bar down with it, and the same
   heading then sits at two different heights on two screens. Better to lose
   the tail of a long name than the rhythm of every screen. */
.pane-bar-title,
.pane-bar-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pane-bar-title { font-family: var(--font-display); font-size: var(--text-10); font-weight: 500; }
.pane-bar-sub { font-size: var(--text-4); color: var(--faint); margin-top: 1px; }
.pane-bar-aside { display: flex; align-items: center; gap: var(--space-2); flex: 0 0 auto; margin-left: auto; }


/* ------------------------------------------------------------- side pane --
   The fourth column, on every screen. It is the selection — one row, one item
   — or, with nothing picked, an overview of the slice in view. Fixed width
   because it holds one thing at a time and a column that resized with its
   contents would move the list beside it.

   Fixed, but not fixed here: the number is `--side-w` in tokens.css, because
   it is the one shell width two apps want two different answers to. An app
   that wants a wider pane sets that token in its own stylesheet and this rule
   is untouched.

   A pane whose empty state is a sentence telling you to click something is a
   column that is blank on the screen the app opens on. Show the overview
   instead, built from the same pieces as a selection. */

.screen-side {
  flex: 0 0 var(--side-w);
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--surface-pane);
  border-left: 1px solid var(--hairline);
}
.screen-side:empty { display: none; }

.side-head-row {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--pane-y) var(--pane-x-side) var(--space-3);
  border-bottom: 1px solid var(--rule);
}
.side-mark { display: grid; place-items: center; flex: 0 0 auto; color: var(--accent-2); }
.side-ident { min-width: 0; flex: 1 1 auto; }
.side-title { font-family: var(--font-display); font-size: var(--text-8); font-weight: 500; }
.side-sub {
  font-family: var(--mono);
  font-size: var(--text-2);
  color: var(--faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.side-head-row .row-avatar { background: var(--accent-2-cont); color: var(--on-accent-2-cont); }

.side-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: 0 var(--pane-x-side) var(--space-5);
}
/* A drilled-in pane has no head — the level to its left is already titled — so
   the top inset the head was carrying has to come from somewhere, or the first
   section sits against the top of the column. */
.screen-side > .side-body:first-child { padding-top: var(--pane-y); }

/* Held at the bottom of the column rather than at the bottom of the scroll,
   because a pane whose body is longer than the column would otherwise hide its
   one link below the fold. */
.side-foot {
  flex: 0 0 auto;
  padding: var(--space-3) var(--pane-x-side) var(--space-4);
  background: var(--surface-pane);
  border-top: 1px solid var(--rule);
}
.side-body:has(+ .side-foot) { padding-bottom: 4px; }
.side-foot .side-action { margin-top: 0; }
