/* NIX — the shell: the rail and the panel
   ══════════════════════════════════════════════════════════════════════════
   THE SITE HAS TWO STATES AND ONE CLASS SAYS WHICH. `is-nav` on <body> means
   the rail is out. It is restored before first paint by views/partials/head.xml
   from localStorage, so a rail that was open stays open across a navigation —
   which is the whole reason the rail exists rather than a menu.

   Four arrangements, and every rule in this file belongs to one of them:

     SHUT, desktop     the rail's grid track is 0 and the page has the window.
                       The panel has no ground and no corners: it IS the page.
                       The ring sits at the left edge.
     OPEN, desktop     the track becomes --rail-w and the page gives that width
                       back. The panel takes --nix-panel and a radius, so it
                       reads as a second panel beside the rail. The ring travels
                       out to the rail's far edge, in step with it.
     SHUT, phone       identical to shut on a desktop. Nothing to do.
     OPEN, phone       the rail COVERS instead of pushing: two columns of 260
                       and whatever is left is a corridor, not a room. The
                       shell keeps its single column, the rail goes fixed over
                       the page, and the panel gives up its ground and corners
                       with the gutter that made them read.

   THE ANIMATION IS THE COLUMN TRACK ITSELF, not a transform and not a slide.
   A fixed rail sliding over the page would cover the left of it; a transform
   would leave the page's own width unchanged, and every full-bleed band on it
   would still be measuring the whole window and running off the side.

   That is also why so much of this file is `body.is-nav <something on a page>`:
   a band that reaches to the window's edges has to stop doing that the moment
   the window stops being the box it is in. Those rules are collected here
   rather than left in each page's sheet, because they are all one mistake and
   they should all stop being it in one place.

   Cross-document view transitions are here too: with the rail open the panel
   is the only thing that changes, so it fades and the rail holds still; with
   it shut nothing is named except the ring, so the whole page crosses.
   ══════════════════════════════════════════════════════════════════════════ */
/* ── The nav shell ──────────────────────────────────────────────────────────
   The rail and the page, side by side, in the arrangement the marketplace
   uses — so opening the menu turns this page into the same room rather than
   dropping a screen over it.

   THE ANIMATION IS THE COLUMN TRACK ITSELF. Closed, the rail's column is 0
   wide and the page has the window; open, the track becomes 260 and the page
   gives that width back. Sliding a fixed rail over the page instead would
   cover the left of it, and pushing with a transform would leave the page's
   own width unchanged — its full-bleed bands would still be measuring the
   whole window and would run off the side.

   The gutter and the gap animate with it. At rest they are zero, which is
   what keeps the closed page edge to edge exactly as it was. */
/* ══ TWO WAYS TO ARRIVE ═════════════════════════════════════════════════════
   These pages are separate documents and always will be — each one carries a
   preloader, a three.js scene or a wizard in its own <script>, and a router
   that swapped innerHTML would leave every one of those unrun. What changes is
   only what the reader SEES between the two, and a cross-document view
   transition buys exactly that without a router.

   With the rail OPEN it reads as an app: the rail is one element in both
   documents, so naming it holds it still while the panel beside it changes.
   Nothing slides, nothing reloads, the links do not blink.

   With the rail SHUT it reads as a page: nothing is named except the ring, so
   the default root transition fades the whole thing out and the next one in,
   and the one control that survives is the way back to the menu.

   navigation: auto opts BOTH documents in — same stylesheet, so any two pages
   here already agree. Browsers without it simply navigate, which is what they
   did before. */
@view-transition { navigation: auto; }

/* Named, so the browser pairs them across the two documents rather than
   cross-fading them with the rest of the page. */
.nav-rail { view-transition-name: nix-rail; }

.index,
.page { view-transition-name: nix-panel; }

/* .index and .page never appear in the same document — one is the index's
   scroller, the other is AB01's — so the shared name is never a duplicate. */
.site-menu__trigger { view-transition-name: nix-ring; }

/* The panel is the only thing that moves, and it moves by fading. No slide:
   the rail stays put and a panel that slid under a rail that did not would
   read as two pages, which is the thing this is for. */
::view-transition-old(nix-panel),
::view-transition-new(nix-panel) {
  animation-duration: 260ms;
  animation-timing-function: var(--ease-out-expo);
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 320ms;
  animation-timing-function: var(--ease-out-expo);
}

/* The rail and the ring hold still rather than being animated at all — a
   cross-fade between two identical images is still a flicker. */
::view-transition-group(nix-rail),
::view-transition-group(nix-ring) { animation: none; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* ── THREE STATES, ONE NUMBER ─────────────────────────────────────────────
   The rail is a panel, a strip of icons, or gone, and every rule that has to
   know which reads --rail-track rather than testing the body's class for
   itself. There were three copies of the grid, and everything that measured
   the panel — the orbit, the bleeding bands, the release row — was written
   against --rail-w and so was only right in one of the three states.

   The two are DECLARED IN tokens.css, with the other rail measures — this
   sheet only says what each state does to them, because portal.css spends
   them as well and a token whose only declaration is in a sheet that happens
   to be linked first is a token that resolves to nothing the day the order
   changes. The default there is the strip, which is what a reader who has
   never touched the seam gets. */
body.is-nav { --rail-track: var(--rail-w); }

/* GONE, AND ONLY THE SEAM IS LEFT. Dragged past the strip: the navigation
   leaves the page and the blue line on the panel's edge is the way back — the
   app's own third state, and the reason the seam is a control and not just a
   size.

   THE GUTTER GOES WITH IT. A gap between two panels when there is only one
   panel is 8px of nothing on the left and 8 on the right of the window: the
   page sat visibly off centre with the navigation away, which is the whole of
   "it affects the padding". Zero here, and the panel is inset from all four
   edges by the same --shell-gap. */
/* :not(.is-nav), and not because rail.js would ever set both. The phone's ring
   is bound by each page's own inline script, which toggles is-nav and has
   never heard of a third state — so the two classes CAN be on the body at
   once, and the reader who opened the menu that way must not get a rail whose
   track is still zero. Spelt into the selector rather than left to source
   order, which is how a rule that already lost a tie gets moved and wins it. */
body.is-shut:not(.is-nav) { --rail-track: 0px; --rail-gap: 0px; }

/* TWO TRACKS, NOT THREE. The middle one was the handle's, and the handle is no
   longer in the grid at all — see .rail-grip below, which is positioned
   against the shell so that it can sit ON the panel's edge rather than in the
   space beside it. Its zero-width track cost a second gutter all the same: the
   panel was inset 24 on the left and 8 on the right. */
.nav-shell {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: var(--rail-track) minmax(0, 1fr);
  column-gap: var(--rail-gap);
  padding: var(--shell-gap);
  box-sizing: border-box;
  transition: grid-template-columns 420ms var(--ease-out-expo),
              column-gap 420ms var(--ease-out-expo),
              padding 420ms var(--ease-out-expo);
}

/* NOTHING IN THE COLUMN WHEN THERE IS NO COLUMN. visibility, not display: the
   rail keeps its box so the grid's width transition has something to animate,
   and visibility is what takes its links out of the tab order — a reader
   tabbing into a rail that is not on screen is the defect overflow alone does
   not fix. */
body.is-shut:not(.is-nav) .nav-rail { visibility: hidden; }

/* THE SAME SHELL, SAID LOUDER. On the native pages — the blog, the login —
   the shell is #wrapwrap, and Odoo's own frontend bundle is linked AFTER this
   file and styles that element by id. A class cannot outrank an id, so the two
   rules above are repeated at id weight for the one element that needs it.
   Nothing else changes; if these two ever drift from the two above, the blog
   is the page that will show it. */
/* THE NATIVE FOOTER IS HIDDEN, NOT REMOVED. It carries the Odoo copyright and
   `web.brand_promotion`, and other modules — Planning's frontend layout among
   them — xpath into that subtree. Deleting the node from the view made their
   inherit unresolvable and took the whole registry down with it. Left in the
   tree, taken off the page here. Our own foot is inside the scroller. */
#wrapwrap > footer#bottom { display: none; }

/* THE FOOT GOES UNDER THE POSTS, NOT BESIDE THEM. It was not floats — it was
   portal.css, which was written when "public" meant the login page: it laid
   that scroller out as a flex ROW above 920px and capped every child at 42rem.
   The blog inherited both and came out as a 500px column of posts with the
   foot standing beside it. Those two rules now name the login by what is in
   it (see portal.css), and this is the backstop under them: the panel's
   children are full width, one under the next.

   A COLUMN, so the foot stays at the bottom. Three published posts do not fill
   a panel, and as plain blocks the foot climbed up under them and left the rest
   of the panel empty beneath it. The content grows into whatever is left and
   the foot is pushed to the bottom edge; on a long page nothing changes,
   because there is nothing left to grow into. */
#wrapwrap.nav-shell main.page {
  display: flex;
  flex-direction: column;
  /* STRETCH, and this is the whole reason the blog looked narrow. .page sets
     align-items:center for the standalone pages, where every child is a
     measured column that wants centring. Here the child is #wrap — the blog's
     whole body — and centring made it shrink to its content: 600px of cards in
     a 976px panel, two per row, offset to the right of everything above it. */
  align-items: stretch;
}

#wrapwrap.nav-shell main.page > * {
  float: none;
  clear: both;
  width: auto;
  max-width: none;
  flex: none;
}

/* THE BLOG'S OWN SCREEN, AND THEN THE FOOT. flex:1 0 auto grows #wrap to fill
   what is left of the scrollport, which puts the foot at the bottom of the
   FIRST screen — three posts and a site map on one view, with nothing to
   scroll to. min-height claims the whole panel instead, so the foot begins
   exactly where the screen ends and a reader who wants it goes looking. */
#wrapwrap.nav-shell main.page > #wrap {
  flex: 1 0 auto;
  min-height: 100%;
}

#wrapwrap.nav-shell main.page > .index-foot { display: grid; margin-top: auto; }

/* Bootstrap gives every <p> a 1rem bottom margin, and the copyright is a <p>.
   Inside a centred flex row that margin is counted in the height, so the line
   sat higher than the mark beside it. */
#wrapwrap .index-foot__legal { margin: 0; }

/* The nav sat a tenth of the way down the panel: the container it is drawn
   above carries bootstrap's py-4 and the pill added its own margin on top of
   that. One of the two is enough. */
#wrapwrap.nav-shell .website_blog .container { padding-top: 0; }

/* THE RAIL IS NOT A DOCUMENT. Odoo's frontend styles every <a> on the page,
   and two of those reach into a rail that was written for a document with no
   bootstrap in it: underline on hover, and a line-height that made the current
   item taller than the four around it. Restated here at id weight — the values
   are the rail's own, not new ones. */
#wrapwrap.nav-shell .nav-rail__item,
#wrapwrap.nav-shell .nav-rail a {
  text-decoration: none;
  line-height: 1.3;
}

#wrapwrap.nav-shell .nav-rail__item { padding: 9px 12px; }

/* NO COLOUR SWEEP OVER THE BLOG. There was one here — bootstrap's white
   utilities knocked out, the post cards given --nix-surface, the search field
   given a dark ground — and it changed more than it fixed: the bar it was
   aimed at is not painted by either utility, so the white stayed and the input
   inside it went dark on white. website_blog is Odoo's markup and Odoo's
   bundle, and skinning it blind from selector names is how a page ends up half
   reskinned. It gets a deliberate pass of its own, against the real markup,
   or it gets Odoo's own look until then. What stays below is structure: the
   shell, the panel, and the foot sitting under the posts rather than beside
   them. */
#wrapwrap.nav-shell {
  display: grid;
  grid-template-columns: var(--rail-track) minmax(0, 1fr);
  column-gap: var(--rail-gap);
  padding: var(--shell-gap);
}

.nav-rail {
  min-width: 0;
  /* overflow AND visibility. overflow keeps the contents from spilling out of
     a zero-width column; visibility is what takes its links out of the tab
     order, which overflow alone does not do — a keyboard user would otherwise
     tab into a menu that is not on screen. */
  overflow: hidden;
  /* AND SCROLLS ON THE BLOCK AXIS. `overflow: hidden` is what keeps the
     contents inside a zero-width column while the rail is shut; on a short
     screen it was also what quietly cut the last thing in the rail off the
     bottom, and the last thing in the rail is the way in. */
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--shell-pad);
  box-sizing: border-box;
  /* --nix-rail, not --nix-surface. They are the same value in dark and they
     are not in light, where the rail is the one true white on the page and a
     card is a step down from it. */
  background: var(--nix-rail);
  border-radius: var(--radius-xl);
  padding: var(--shell-pad);
}

/* ── SHUT: THE SAME RAIL, DOWN TO ITS ICONS ───────────────────────────────
   Every destination stays on screen and one click away; what goes is the
   words beside them, the language pair and the theme's label — a 64px column
   cannot hold a sentence.

   The items centre their glyphs and lose their text rather than being hidden:
   an icon rail whose icons are in a different place from the full rail's is
   two rails, and the eye has to find the row again every time it is opened. */
body:not(.is-nav) .nav-rail__label,
body:not(.is-nav) .nav-rail__caption,
body:not(.is-nav) .nav-rail__tools {
  display: none;
}

/* ── OPEN, THE PANEL CARRIES ITS TITLE AND NOTHING ELSE ───────────────────
   All three of these were hidden out at 260px, on the argument that four
   words beside four glyphs say what the column is by themselves. The head is
   back, by the operator's decision, and it is the half of that argument that
   was weakest: the rail is one of two panels on the page and the other one
   says Account at the top of it. A panel that names itself is not chrome
   introducing a list — it is the title bar the app puts on every panel, and
   without it the two rails were titled inconsistently.

   The CAPTION stays hidden, and that half of the argument holds: "Menu" over
   four destinations names the list a second time, under a head that already
   named it.

   The rule under the mark comes back with the head — it is the head's own
   bottom edge, not a divider in the list, and a title bar that just fades
   into the first destination is the thing the hairline was there to stop. */
body.is-nav .nav-rail__caption {
  display: none;
}

/* THE WORD IS TAKEN OFF THE SCREEN, NOT OUT OF THE LINK. `display: none` on
   the span took the only text in the anchor with it, and a link whose text is
   gone has no accessible name — the whole strip announced itself as four
   unnamed links and a graphic. Clipped instead: nothing is drawn, the name
   survives, and js/rail.js reads that same text back as the tooltip. */
/* The login's half is named by class rather than by tag, and that is not
   tidiness: signed in, that button holds a badge, a name and an address, and
   `.nav-rail__login span` clipped all four of them — including the initial,
   which is the only thing the strip has to show. One element is the label; the
   badge is not inside it. */
body:not(.is-nav) .nav-rail__item span,
body:not(.is-nav) .nav-rail__login-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The head is a grid and the items are flex, so each is centred in its own
   vocabulary — one property apiece rather than one rule that half applies.

   The row it used to reserve under the mark was the ring's, and the ring is
   not in this column any more. */
body:not(.is-nav) .nav-rail__head {
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  padding-inline: 0;
  /* The open head's top air is for a title bar with a word in it; in a 58px
     strip there is no word, and 12 more above a lone mark pushes the whole
     column down for nothing. */
  padding-top: 0;
}

/* A SQUARE, NOT A SLIVER, AND IT IS THE APP'S SQUARE. The rail is
   `align-items: center` while it is a strip, which makes every child shrink to
   its contents — and with the label clipped the contents of an item are a
   glyph. The hover ground and the current item's ink slab were being painted
   20 wide and 38 tall: a vertical bar behind an icon, in a column of four of
   them.

   --rail-item is 34, which is agent-base's own .rail-item, in a 58px strip,
   which is its own --rail-width. min-height has to be restated because the
   base rule floors every row at 44 for a thumb — there is no strip on a phone,
   so the floor has nothing to protect here and it was the other half of what
   made these boxes the wrong shape. */
body:not(.is-nav) .nav-rail__item,
body:not(.is-nav) #wrapwrap.nav-shell .nav-rail__item,
body:not(.is-nav) .nav-rail__login {
  position: relative;
  justify-content: center;
  width: var(--rail-item);
  height: var(--rail-item);
  min-height: 0;
  padding: 0;
  margin-inline: auto;
  border-radius: var(--radius-sm);
}

/* 18 IN A 34 BOX, which is the ratio the app draws: at 20 the glyph crowds
   its own ground and the hover square reads as a border around the icon
   rather than a seat under it. */
body:not(.is-nav) .nav-rail__item svg,
body:not(.is-nav) .nav-rail__login svg {
  width: 18px;
  height: 18px;
}

/* AND THE ACCOUNT IS A CIRCLE. It is a person, not a destination — the app
   draws it as an avatar and an avatar is round everywhere. It was the one
   filled square in a column of square seats, which read as a button that had
   been left switched on. */
body:not(.is-nav) .nav-rail__login {
  border-radius: var(--radius-pill);
}

/* The strip's rhythm is the app's too: 8 between seats, 12 top and bottom. */
body:not(.is-nav) .nav-rail__nav {
  gap: var(--space-1);
}

/* THE WAY IN STAYS ON SCREEN. It used to be hidden with the tools, on the
   grounds that a 64px column cannot hold a button — which is true of the
   button's LABEL and not of the button. It is the one thing in this rail that
   is not a place on the site and the one a reader comes to the rail for; a
   strip that shows four destinations and no account is a strip missing the
   only item that changes what the site does. It keeps its ink ground, so it
   still reads as the one filled control in the column.

   margin-top:auto moves here from .nav-rail__tools, which is the block that
   carries it in the open state and is not on the page in this one — without
   it the button rode up under the fourth destination instead of sitting at
   the foot where it does when the rail is out.

   ON THE WRAPPER, NOT ON THE BUTTON. Signed in the button lives inside
   .nav-rail__account with the menu beside it, and an auto margin on a child of
   a wrapper that has no spare height is an auto margin that resolves to
   nothing. Both are named so the signed-out button, which has no wrapper, is
   still pushed. */
body:not(.is-nav) .nav-rail__login,
body:not(.is-nav) .nav-rail__account {
  margin-top: auto;
}

/* 12 EITHER SIDE OF A 34px SEAT IS THE 58, and that is not a coincidence —
   it is how agent-base derives its own rail width, so its strip's inset is
   the --space-3 every panel there already pads by. The override that stood
   here spent 8 and left the seats off-centre by two. --shell-pad is this
   module's name for the same 12, and it is what .nav-rail already pads by,
   so there is nothing left to say. */
body:not(.is-nav) .nav-rail {
  align-items: center;
}

/* THE TWO PANELS TAKE THEIR COLUMNS BY NAME. There is nothing else in the grid
   now — the handle is positioned against the shell rather than placed in it —
   but the rail is built by five different templates and the panel goes by four
   different class names, and saying which column each is in costs two rules
   and cannot be got wrong by a page that adds a sixth element to the shell.

   .index, .page and .mk are the three names a page's scroller goes by here;
   #wrapwrap's own main is the fourth, on the portal leg. */
.nav-rail { grid-column: 1; }

.nav-shell > .index,
.nav-shell > .page,
.nav-shell > .mk,
#wrapwrap.nav-shell > main { grid-column: 2; }

/* ── The seam is a switch ─────────────────────────────────────────────────
   js/rail.js puts this on every page that has a shell.

   IT SIZED THE RAIL ONCE AND IT DOES NOT NOW. The open rail is one width, so
   what this control does is take it a step smaller — panel, strip, gone — and
   a leftward drag on it hides the rail outright. The drawing is unchanged
   because the gesture that matters is unchanged: it is still the thing you
   press and pull at the edge of the navigation.

   THE APP'S OWN HANDLE, MEASURED OFF ITS OWN SHEET. agent-base's .resizer is
   a 16px column with a 3×42 pill in the middle of it, neutral at rest, and it
   turns --accent-2 and grows to 64 under a pointer or mid-drag. What stood
   here first was a 2px hairline inset 12 from the top and the bottom — which
   is not a handle, it is a rule down the whole seam, and that is exactly what
   it looked like: a white line splitting the page. A pill, not a line: a line
   is a border and nobody pulls on a border.

   AND IT SITS ON THE PANEL'S EDGE, WHICH IS WHERE THE APP'S SITS. It was a
   grid item centred in the gutter — floating in the space between two panels,
   touching neither. The app draws its resizer ON the panel it resizes, right
   over the edge, and that is what makes it read as belonging to the panel
   rather than as a mark on the background.

   POSITIONED AGAINST THE SHELL, so it costs the layout nothing: it was a grid
   item, and even at zero width its track carried a second gutter — the panel
   was inset 24 on the left against 8 on the right, which is the asymmetry you
   could see with the rail away. Out of flow, the panel is inset the same on
   all four edges and the handle is drawn on top of it.

   The left is the panel's own left edge, spelt from the same two variables
   the grid spends, so the two cannot drift: --shell-gap is the shell's
   padding, --rail-track the rail, --rail-gap the gutter (0 when there is no
   rail to leave a gutter beside). Less half the 16px box, so the PILL is
   centred on the edge rather than starting at it. */
.rail-grip {
  position: absolute;
  z-index: 3;
  top: var(--shell-gap);
  bottom: var(--shell-gap);
  left: calc(var(--shell-gap) + var(--rail-track) + var(--rail-gap) - 8px);
  width: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: col-resize;
  touch-action: none;
  background: transparent;
  /* In step with the track it rides, or the handle arrives at the panel's new
     edge a third of a second after the panel does. */
  transition: left 420ms var(--ease-out-expo);
}

.rail-grip::before {
  content: "";
  width: 3px;
  height: 42px;
  border-radius: var(--radius-pill);
  background: var(--nix-rule);
  transition: background var(--dur-2) ease, height var(--dur-2) ease;
}

/* AND THIS IS THE ONE COLOUR ON THE SITE. It is agent-base's --accent-2 doing
   the job it does there: saying the handle is live, on the frame where a
   pointer arrives. At rest it stays a hairline, so the page is still
   monochrome until somebody reaches for it. */
.rail-grip:hover::before,
.rail-grip:focus-visible::before,
body.is-sizing .rail-grip::before {
  background: var(--nix-seam);
  height: 64px;
}

/* MID-DRAG, PAST THE FLOOR: the pill shortens to say the release will collapse
   rather than resize, before the release happens. The app says it the same
   way and for the same reason — an outcome you cannot see until it is done is
   an outcome you undo. */
body.is-sizing .rail-grip.is-closing::before { height: 24px; opacity: .5; }

.rail-grip:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 0;
}

/* IT IS THE ONLY CONTROL NOW, SO IT IS ON IN ALL THREE STATES. It used to be
   hidden whenever the rail was not a full panel, on the grounds that 64 was
   the design rather than a preference — which was true and left the strip
   with nothing to grab, so the ring had to exist to undo it. The seam sizes
   the panel, collapses it to the strip, sends it away, and clicking brings it
   back; there is no second control to keep in step with it. */

/* AND THE GRID STOPS EASING WHILE THE POINTER IS DRAGGING IT. 420ms of
   ease-out on every pointermove is a rail arriving where the pointer was a
   third of a second ago — it reads as lag, and on a fast drag it never
   catches up at all. */
/* The handle is on this list for the same reason with a sharper edge: it is
   the thing under the pointer, so 420ms of easing on it is a control that
   visibly lags the finger holding it. */
body.is-sizing .nav-shell,
body.is-sizing #wrapwrap.nav-shell,
body.is-sizing .rail-grip {
  transition: none;
}

/* A drag is a drag, not a text selection: without this the labels in the rail
   highlight blue as the pointer sweeps across them. */
body.is-sizing {
  user-select: none;
  -webkit-user-select: none;
}


/* ── The rail's parts ─────────────────────────────────────────────────────
   Worn by every page that has a rail, including the store's — which is
   always out, and so keeps its own .mk-rail panel while its contents are
   these. They lived in the store's own sheet until 2026-08-15, which was
   invisible for as long as one stylesheet served every page. */
/* Brand, section, menu — the panel's own title bar. Grid rather than flex so
   the label sits between two fixed ends and stays put when the trigger grows
   a focus ring. */
.nav-rail__head {
  display: grid;
  /* TWO TRACKS, FOR THE TWO THINGS IN IT: the mark and the label. There were
     three, for a trigger that is not in this head — it is a sibling of the
     rail, positioned against the shell. The spare track absorbed the free
     space, which meant the label never ran out of room and its own
     text-overflow could not fire. */
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 10px;
  /* Space, not a rule. The app separates a panel's title from its
     contents with the gap alone; a hairline here would be the only line on
     the page. */
  padding-bottom: var(--shell-pad);
  /* THE SAME 12 THE DESTINATIONS PAD BY, so the mark's left edge lands on the
     x every icon under it starts at. Without it the head sat one inset out
     from the list it titles — the rail's own 12 and nothing more — and the
     mark hung to the left of a column that was otherwise flush.
     The strip resets this to 0 below, where the head is centred instead. */
  padding-inline: var(--shell-pad);
  /* And the panel's title gets air above it rather than starting hard against
     the rail's padding. Reset in the strip for the same reason. */
  padding-top: var(--shell-pad);
}

.nav-rail__brand { display: flex; line-height: 0; }

.nav-rail__brand img {
  display: block;
  /* The app's .mark is 24 square in its rail; this is the same badge at the
     same size, so the two products' chrome measures alike. */
  width: var(--rail-logo);
  height: var(--rail-logo);
}

/* AND IT IS ICON-SIZED WHILE THE RAIL IS OUT. --rail-logo is 24, which is the
   app's badge and the size the strip and the shut-state ring are both measured
   against — neither is touched here. Beside a 13px word, over a list of 16px
   glyphs, 24 reads as a third size in a head that has only two things in it;
   16 puts the mark on the column the icons already set. */
body.is-nav .nav-rail__brand img {
  width: 16px;
  height: 16px;
}

.nav-rail__label {
  font-size: 13px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--nix-text-soft);
  /* A long word in a 260px panel would otherwise push the trigger out of the
     row rather than shortening itself. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}


/* THE RULE UNDER THE MARK. A hairline the width of a seat, belonging to
   neither side of it: above it the brand, below it the list of places. It was
   the account rail's alone — and lived in portal.css, which four of the five
   rails never link, so putting one in the public markup meant moving the rule
   that draws it here. A shared part styled in a leg-specific sheet is a part
   that is only styled on one leg.

   --chip-tint rather than --nix-rule: the rail is the one true white in light,
   and the rule token is measured against the page. */
.nav-rail__sep {
  flex: none;
  width: var(--rail-item);
  /* Half a pixel: at 1 the rule reads as a drawn line in a system whose whole
     brief is that there are none. On a 2x screen it is one device pixel. */
  height: 0.5px;
  margin-block: var(--space-1);
  background: var(--chip-tint);
}

/* Out, it spans the rail rather than a seat: a 34px stub under a 260px column
   reads as a fragment of something, not as a division of it. */
body.is-nav .nav-rail__sep {
  width: auto;
  align-self: stretch;
}

/* THE GROUP'S NAME. Same type as .nav-rail__label above it — 13px, uppercase,
   quiet — because they are the same kind of thing said about two different
   objects: one names the place (Website, and Account on /my's own rail), one
   names the list under it.

   The inline padding matches the destinations' own, so the caption's first
   letter lands on the same x as every word beneath it rather than sitting a
   few pixels out from a list it is supposed to be labelling. */
.nav-rail__caption {
  margin: 0 0 4px;
  padding-inline: 12px;
  font-size: 13px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--nix-text-soft);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav-rail__nav { display: flex; flex-direction: column; gap: 2px; }

.nav-rail__item {
  /* Icon and word on one line. The icon is a fixed square so every label
     starts at the same x whatever glyph sits beside it — a nav whose icons
     set their own width has a ragged left edge. */
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  color: var(--nix-text-soft);
  text-decoration: none;
  font-size: 14px;
}

.nav-rail__item svg {
  flex: none;
  width: 16px;
  height: 16px;
  /* The glyph rides the text colour, so hover and the current state carry it
     without a second rule for the icon. */
  color: currentColor;
}

/* ── THE LIST ARRIVES, ONE ROW BEHIND THE NEXT ────────────────────────────
   The same entrance agent-base plays on its own rail — 10px of rise under a
   fade, 70ms between rows — asked for by the operator after the app's
   onboarding, where the account step fades in as one block. A column of four
   is short enough that a stagger reads as the list assembling rather than as
   four separate animations, and the eye lands on Home first, which is where
   the page wants it.

   On the .nav-rail__nav rather than on every .nav-rail__item, so the seat at
   the foot — the login card, the account row — is not in the sequence: it is
   not one of the places, and it carries the reader's own name.

   The delays stop at the fifth row. A rail with a second storey has seven,
   and 70ms apiece would still be arriving half a second after the reader
   pressed the row that opened it.

   No reduced-motion block of its own: base.css floors every duration and
   every delay in the document, which covers this. */
@keyframes nix-rail-item-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

/* IT PLAYS WHEN A LIST APPEARS, which is not the same as when a page loads.
   Written as "on load" it replayed on every navigation — the same level
   assembling itself again on the far side of a link — and the fix for that,
   marking the tab as having seen it, killed the case that matters most:
   opening a level in place, where nothing loads and the rows simply were
   already there.

   So the first arrival in a tab animates the rail it lands on, and after that
   the entrance belongs to the act that reveals a list: js/rail.js puts
   .is-opening on whichever nav it just brought on screen. */
body:not(.is-rail-seen) .nav-rail__nav .nav-rail__item,
.nav-rail__nav.is-opening .nav-rail__item {
  animation: nix-rail-item-in var(--dur-3) var(--ease-out-expo) both;
}

.nav-rail__nav .nav-rail__item:nth-child(2) { animation-delay: 70ms; }
.nav-rail__nav .nav-rail__item:nth-child(3) { animation-delay: 140ms; }
.nav-rail__nav .nav-rail__item:nth-child(4) { animation-delay: 210ms; }
.nav-rail__nav .nav-rail__item:nth-child(n+5) { animation-delay: 280ms; }

/* A SEAT CAN BE A <button>, AND A BUTTON BRINGS ITS OWN EVERYTHING. The
   account rail's bell is one where the destinations around it are anchors, so
   without this it came out with the browser's border, the browser's grey fill
   and the browser's font, in a column of five rows that have none of the
   three. The rule existed on the old .nix-apprail__item and was deleted with
   it when that class was retired — the bell was a boxed, greyed row on /my
   for exactly one commit.

   font-family AND NOT `font`, which was the first version of this and is a
   trap: the shorthand resets font-size too, and this selector outranks the
   .nav-rail__item that declares 14px — so the one row that needed fixing
   would have come out at the inherited size instead. The UA's own font-size
   loses to that class without any help.

   The colour is not restated either: .nav-rail__item sets it, and an author
   rule beats the UA sheet. Only what the UA gives a <button> that a link
   never had is undone here. */
button.nav-rail__item {
  border: none;
  background: transparent;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}

/* --chip-hover, NOT --nix-chip. In light the chip token and the rail are
   both #FFFFFF, so the hover ground was white on white and the only thing
   that moved was the label's colour — on the one control on the page whose
   whole job is to answer the pointer. --chip-hover is the chip mixed a step
   toward the text, which is a real step in both themes. */
/* --chip-tint, the 8% step. --chip-hover is 16 and exists for a card that
   is ALREADY tinted; on the rail, where the ground is flat, 16 paints a grey
   slab under an item whose selected state is full ink — the two states then
   differ in degree rather than in kind, and a pointer running down the rail
   lights four rows of nearly-selected. */
.nav-rail__item:hover { background: var(--chip-tint); color: var(--nix-text); }

/* The current filter carries the ink surface, which is the strongest thing
   this palette has to say "you are here" without reaching for a colour. */
.nav-rail__item.is-on {
  background: var(--nix-ink);
  color: var(--nix-on-ink);
}

/* ── THE ACCOUNT MENU ─────────────────────────────────────────────────────
   The popover the signed-in seat at the foot of the rail opens: where you go,
   what language you read in, which theme, and the way out.

   MOVED HERE FROM portal.css, and it had to be. It was written for /my, whose
   layout links that sheet; the same menu now opens from the public rails, and
   four of those five are standalone documents that never link it. Same reason
   .nav-rail__sep and .nav-rail__back moved before it — a shared part styled in
   a leg-specific sheet is a part that is only styled on one leg.

   A CARD OVER THE ROW, INSIDE THE RAIL. It opened OUTSIDE the rail once, at
   the rail's right edge — fixed, hung off <body>, and placed by script from
   the account button's rectangle — because the rail is `overflow-x: hidden`
   and its open width was whatever the reader had last dragged it to. That
   arrangement shipped one revision in which every pixel of the menu sat past
   the clip: the rail ended at x=66 and the popover ran 66 to 251, so what
   reached the screen was the sliver at the boundary, reported correctly as
   "a line".

   The open rail is one fixed width now, so the menu can be what it looks
   like: a card the width of the account row, stacked on top of it, positioned
   against .nav-rail__account and nothing else. left and right rather than a
   width, so it matches the row it belongs to without either of them naming a
   number. It travels with the rail's own scroll for free, which the fixed box
   needed a capturing scroll listener to fake. */
.nix-popover {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 6px);
  display: none;
  flex-direction: column;
  padding: 6px;
  border-radius: var(--radius-chip);
  background: var(--nix-surface);
  box-shadow: var(--lift-3);
  z-index: var(--z-grain);
}

.nix-popover[data-state="open"] {
  display: flex;
}

.nix-popover__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 12px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--nix-text);
  font-size: 13px;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.nix-popover__item:hover {
  background: color-mix(in srgb, var(--nix-text) 8%, transparent);
  color: var(--nix-text);
  text-decoration: none;
}

.nix-popover__label {
  display: flex;
  align-items: center;
  gap: 10px;
}

.nix-popover__label svg {
  width: 16px;
  height: 16px;
  color: var(--nix-text-soft);
}

.nix-popover__item:hover .nix-popover__label svg {
  color: var(--nix-text);
}

/* THE LANGUAGE ROW IS NOT A BUTTON, it is a row with two of them in it, so
   it must not answer the pointer the way its neighbours do: a hover ground
   under a row you cannot press is an invitation to press it. The two words
   inside keep their own hover, which is where the pressing happens.

   default cursor for the same reason — the hand belongs to EN and ES. */
.nix-popover__lang {
  cursor: default;
}

.nix-popover__lang:hover {
  background: transparent;
}

/* The pair sits where the theme's dot sits in the row below it, so the two
   settings line up on their right edge rather than each ending wherever its
   own control happens to be wide. */
.nix-popover .lang {
  flex: none;
}

/* The dot indicator inside the Theme row is a passive span — the row is
   the button — but it keeps the toggle's fills-when-dark treatment. */
.nix-popover .theme-toggle {
  display: inline-block;
  pointer-events: none;
}

html[data-theme="dark"] .nix-popover {
  box-shadow: 0 0 0 1px var(--nix-rule);
}

.nix-popover__item:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* WHEN THERE IS SOMEBODY SIGNED IN, THE FOOT'S TWO SETTINGS ARE IN THE MENU,
   AND THIS SHEET IS NOT WHAT DECIDES THAT. It was, for one revision — a
   `body:has(.nav-rail__login--me)` rule hiding .nav-rail__tools — and hiding
   it was the wrong half of the job: the pair was still IN the document, so
   every signed-in page carried two elements with id="theme-toggle" and two
   with id="lang". The templates gate the pair on _is_public() instead, which
   is the same outcome with one copy in the page. See views/partials/rail.xml.

*/
/* ── THE WAY BACK OUT OF A LEVEL ──────────────────────────────────────────
   Quieter than a destination on purpose: it is not a place, it is the door
   back to the list you came from. Moved here from portal.css, where it was
   written for the account's "Back to website" — /agent-base needs the same
   row above its sections and that page is a standalone document that never
   links portal.css. Same reason .nav-rail__sep moved before it. */
.nav-rail__back {
  margin-bottom: var(--space-1);
}

body.is-nav .nav-rail__back {
  color: var(--nix-text-quiet);
}

/* ── A SECOND LEVEL, FOR A PAGE THAT IS ALSO A PLACE ──────────────────────
   /agent-base is one row in this menu and thirteen sections underneath it,
   and those sections used to live in a floating pill over the page — a
   second navigation, in a second shape, for the same document. Pressing the
   row you are already on drills into them instead, and the pill goes.

   Not on the page until it is asked for, and only where there are words to
   read: a strip is five glyphs and this list has none, so drilling into it
   there would leave a column of nothing. The rail opens first — rail.js does
   that — and only then is there a level two.

   ON THE BODY, NOT ON THE RAIL, and that is not a detail: the drill survives
   navigation — pressing "Agent Base" from any page asks for it and the page
   arrives already there — so the class has to be written by the boot script
   in views/partials/head.xml, which runs immediately after <body> opens and
   before the rail element exists. A class on the rail could only be added a
   frame later, which is level one drawn and then swapped. */
.nav-rail__drill {
  display: none;
}

/* IN THE STRIP TOO, AND THAT IS THE POINT OF THE GLYPHS. These three rules
   each asked for .is-nav — the rail out — because a second level used to be
   words and nothing else, so a drilled strip would have drawn a column of
   blanks. rail.js paid for that by forcing the panel open on every drill,
   which is the rail resizing itself behind a reader who had put it at a
   width on purpose. Every row in every second level carries an icon now, so
   the strip can show a level the same way it shows level one: glyphs, with
   the words clipped. */
/* SHOW THE ONE THAT WAS ASKED FOR — the other direction, and the direction
   matters. This was written as "hide the level nobody asked for", which meant
   the rule had to name every level that exists: it named two, a third arrived
   (the blog's tags), and pressing Blog drew all three stacked under each
   other, because the name it wrote matched no hide rule. Reported twice, once
   per level added.

   Named this way an unknown name shows NOTHING, which is a menu that failed
   quietly, rather than every list at once, which is a menu that failed loudly
   and wrongly. The store's shelves are unaffected either way: they carry
   .nav-rail__drill--only and are drawn without being asked for. */
body.is-drilled[data-drill="rail-drill-ab"] #rail-drill-ab,
body.is-drilled[data-drill="rail-drill-bs"] #rail-drill-bs,
body.is-drilled[data-drill="rail-drill-ai"] #rail-drill-ai,
body.is-drilled[data-drill="rail-drill-blog"] #rail-drill-blog,
body.is-drilled[data-drill="rail-drill-mk"] #rail-drill-mk {
  display: flex;
}

/* EXCEPT WHERE IT IS THE ONLY LEVEL. The marketplace is not one of the site's
   four places — it is a room inside Agent Base — so its rail has no first
   storey to drill out of: the way up is its top row and the shelves are the
   rest. Waiting for is-drilled there would mean a reader who reached the
   store by any route other than the Market row found an empty rail. */
.nav-rail__drill--only {
  display: flex;
}

/* :has, and the whole menu depends on it. is-drilled is stored, so it is on
   the body of EVERY page the reader opens next — and without this guard the
   rule would hide the only nav on a rail that has no second level at all.
   It asks for the NAMED list rather than any list: arriving at the store with
   Agent Base's sections asked for would otherwise hide the store's own four
   rows to make room for a list that is not in this rail. */
body.is-drilled[data-drill="rail-drill-ab"]:has(#rail-drill-ab) .nav-rail__nav:not(.nav-rail__drill),
body.is-drilled[data-drill="rail-drill-bs"]:has(#rail-drill-bs) .nav-rail__nav:not(.nav-rail__drill),
body.is-drilled[data-drill="rail-drill-ai"]:has(#rail-drill-ai) .nav-rail__nav:not(.nav-rail__drill),
body.is-drilled[data-drill="rail-drill-blog"]:has(#rail-drill-blog) .nav-rail__nav:not(.nav-rail__drill),
body.is-drilled[data-drill="rail-drill-mk"]:has(#rail-drill-mk) .nav-rail__nav:not(.nav-rail__drill) {
  display: none;
}

/* The labels line up with the back row's word rather than with its chevron:
   12px of the seat's own padding, a 16px glyph, the 10px gap. A sub-list that
   starts where its parent's icon starts reads as a sibling of the thing it is
   under.

   ONLY THE ROWS THAT HAVE NO GLYPH OF THEIR OWN. Two of these lists are
   sections of a page and carry none, so they need the indent to line up; the
   marketplace's four shelves carry the same icons their pills do, and
   indenting those would push them 38px past a chevron they are already level
   with. :has is what asks the row rather than the list it is in. */
.nav-rail__drill .nav-rail__item:not(.nav-rail__back):not(:has(svg)) {
  padding-left: calc(12px + 16px + 10px);
}

/* Language and theme at the foot, directly over the login. margin-top:auto
   lives here now rather than on the login, so the leftover space of the
   column opens above the pair and the two of them travel down together —
   the four links stay one uninterrupted list above the gap. */
.nav-rail__tools {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--shell-pad);
  padding: var(--shell-pad) 12px;
}

/* The one thing in this rail that is not a place on the site, so it sits
   apart at the foot rather than seventh in the list.

   IT CARRIES THE PUSH ITSELF NOW, and it has to: the pair above it is drawn
   for a visitor only — signed in, both settings are rows in the account menu —
   so on every signed-in page the block holding margin-top:auto was not in the
   column at all, and the card came to rest directly under the last
   destination with the whole rail empty below it. Reported as exactly that.

   Where the pair IS drawn the push belongs to the pair, or the two of them
   would split the leftover space between them and the settings would float
   halfway up the rail. Hence the sibling rule below rather than a second
   auto. */
.nav-rail__login {
  margin-top: auto;
  /* A centred flex rather than a block with text-align: the row carries a
     min-height of 44 below, and a block would hold its label at the top of
     that box and leave the extra millimetres under it. */
  display: flex;
  align-items: center;
  justify-content: center;
  /* The same 10 between glyph and word that the four destinations above it
     use, so the two halves of the rail are set to one measure. */
  gap: 10px;
  padding: 11px 18px;
  border-radius: var(--radius-sm);
  background: var(--nix-ink);
  color: var(--nix-on-ink);
  text-decoration: none;
  font-size: 14px;
}

/* AND WHERE THE PAIR IS DRAWN, THE PAIR OWNS THE PUSH. Two auto margins in
   one flex column do not both win: they SPLIT the leftover space, which would
   leave the settings floating in the middle of the rail with a gap above them
   and a gap below. The pair is only ever above the card, so a sibling
   combinator is the whole rule. */
.nav-rail__tools ~ .nav-rail__login,
.nav-rail__tools ~ .nav-rail__account {
  margin-top: 0;
}

/* ── THE ACCOUNT ROW AND THE CARD THAT OPENS OVER IT ──────────────────────
   The wrapper exists so the menu has something to be positioned against that
   is exactly as wide as the row it belongs to. It is the containing block and
   nothing else: no ground, no padding, no border.

   THIS IS WHAT THE FIXED RAIL WIDTH BOUGHT. While the reader could drag the
   rail to any width between 180 and 420, a menu inside it could not be
   designed — so it was a fixed box hung off <body> and placed by script from
   the button's rectangle, re-placed on every scroll and every resize, and it
   spent one revision being clipped to a single visible line. At one width it
   is a card stacked on the row, in the flow's own coordinates, and the script
   that placed it is deleted. */
.nav-rail__account {
  margin-top: auto;
  position: relative;
  /* A FLEX COLUMN, AND THAT IS NOT DECORATION. The account seat is a <button>,
     and `width: auto` on a form control is its INTRINSIC width, not the
     containing block's — so the moment the button stopped being a direct child
     of the rail it stopped being stretched and shrank to fit its own longest
     line, the address. It came out about 30px narrower than every row above it
     and than the menu that opens over it, with the gap all on one side.
     Stretch is the default cross-axis alignment here, so this one line is the
     whole fix; a width: 100% on the button would be the same outcome said in a
     place that does not explain itself. */
  display: flex;
  flex-direction: column;
}

.nav-rail__login:hover { opacity: .85; }

/* The glyph is a fixed square for the reason the destinations' is: the icon
   may not set the width of the row it is in. 16 open, 20 in the strip. */
.nav-rail__login svg {
  flex: none;
  width: 16px;
  height: 16px;
  color: currentColor;
}

/* ── SIGNED IN, THE SAME BUTTON CARRIES WHO ──────────────────────────────
   It said "Your account", which the reader already knew. What they could not
   tell from this page is WHICH account — the rail's foot in the app answers
   that, and this is the same seat doing the same job.

   Left-aligned, not centred: two lines of different lengths centred under each
   other read as a caption, and a name is not a caption. The badge is the fixed
   thing on the left and the text runs from it. */
.nav-rail__login--me {
  justify-content: flex-start;
  gap: 10px;
  padding: 8px 10px;
  text-align: left;
}

/* AND IT IS A <button>, WHICH BRINGS ITS OWN EVERYTHING. A button arrives with
   a border, a background, the browser's own font and centred text, and none of
   that is inherited away — every one has to be said again or the one filled
   control in the column comes out grey, in the wrong typeface, a pixel out of
   line with the rows above it.

   THIS LIVED IN portal.css AND THAT WAS THE BUG. It was written when /my was
   the only rail whose seat was a button and the public rails' was an <a>; the
   partial made every seat a button, and four of the five rails are standalone
   documents that never link that sheet — so the same markup came out looking
   like two different controls depending on which leg drew it. Reported as
   exactly that. Same reason .nav-rail__sep, .nav-rail__back and .nix-popover
   moved here before it.

   font-family, not the `font` shorthand — see the note on button.nav-rail__item
   above: the shorthand takes font-size with it and this selector outranks the
   class that sets it. */
button.nav-rail__login {
  border: none;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}

.nav-rail__login-label {
  display: flex;
  flex-direction: column;
  /* A flex item's automatic minimum is its content, so without this the
     address pushes the button wider than the rail instead of ellipsing. */
  min-width: 0;
}

/* THE ADDRESS IS NOT IN THIS BUTTON ANY MORE, and .nav-rail__mail went with
   it. It was the second line, quieter, under the name; the seat is one line
   now. The name cuts rather than wraps — a wrapped name would grow the button
   and push the settings and the destinations up the column. */
.nav-rail__who {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
  line-height: 1.3;
}

/* THE INITIAL, AND IT IS WHAT THE STRIP KEEPS. Round, because it stands where
   an avatar stands and an avatar is round everywhere; inverted, because it sits
   ON the ink ground this button already paints — the same two tokens the other
   way up, so it needs no colour of its own. */
.nav-rail__avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-pill);
  background: var(--nix-on-ink);
  color: var(--nix-ink);
  font-size: 12px;
  line-height: 1;
}

/* ── The strip's tooltip ──────────────────────────────────────────────────
   An icon rail without labels is a memory test, and this one has no labels
   for the same reason it has no words: 64px. The label is not gone though —
   it is clipped, still in the link, and js/rail.js reads it back onto the
   pointer.

   position: fixed, AND THAT IS THE WHOLE REASON THIS IS BUILT BY A SCRIPT AND
   HUNG OFF <body>. The rail is `overflow: hidden` — it has to be, or its
   contents spill out of the column — and it takes a scroll of its own on a
   short screen, which forces the other axis to clip whatever it is set to.
   Anything drawn from inside the rail is cut off at the rail's edge, which is
   exactly where a tooltip has to start. Fixed, off the body, it is outside
   that box; the script puts it beside the row it belongs to and hides it on
   any scroll, because a fixed box does not travel with what it points at.

   pointer-events: none so the box that appears next to the cursor cannot
   itself become what the cursor is over — that is a tooltip that flickers on
   and off as fast as the pointer can enter and leave it. */
.rail-tip {
  position: fixed;
  z-index: 60;
  transform: translateY(-50%);
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  background: var(--nix-ink);
  color: var(--nix-on-ink);
  font-size: 13px;
  line-height: 1.2;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  box-shadow: var(--lift-1);
  transition: opacity var(--dur-2) ease;
}

.rail-tip.is-on { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .rail-tip { transition: none; }
}

/* The page becomes the second panel: same radius and same scroll of its own
   that .mk-panel has, on --nix-panel — a step under the rail beside it and the
   cards inside it, which share --nix-surface. Three grounds, three values:
   page, panel, and the things that sit on the panel.

   AND IT IS THE PANEL IN ALL THREE STATES. This carried body.is-nav, so
   narrowing the rail to a strip did not narrow the content panel — it deleted
   it: the ground, the radius and the inset all went at once and the page's
   contents were left lying on the paper with the strip beside them. What the
   seam changes is how much room the navigation gets. The panel is the panel. */
.index {
  background: var(--nix-panel);
  border-radius: var(--radius-xl);
}

/* AB01 IS THE SECOND PANEL. .page is that document's scroller exactly as
   .index is this one's, so it takes the same ground and the same radius. Its
   own rule rather than a shared selector because the two scrollers agree on
   nothing else — .index is a grid with named areas and .page is a flex column
   with a footer inside it. */
.page {
  background: var(--nix-panel);
  border-radius: var(--radius-xl);
}

/* EVERYTHING ON THAT PAGE THAT MEASURED THE WINDOW. AB01 is full-bleed by
   width:100vw with no negative margin — a 100vw flex item centred in a centred
   column already lands on the viewport, which is true right up until the box
   it is centred in stops BEING the viewport. With a 260px rail beside it every
   one of these ran off the right, where .page clips: the hero mock lost its
   NIX column, the wizard lost its right edge, the reel and the ribbon lost
   their fades and the orbit went off centre.

   They span the panel instead. One rule for all of them rather than a fix per
   section, because they are all the same mistake and they should all stop
   being it at once.

   THE SELECTORS BELOW STILL CARRY `body`, AND NOT AS DECORATION. They used to
   carry body.is-nav, which was doing two jobs at once: saying "only when the
   rail is out", and outranking the plain-class rules in the page's own
   <style> block, which is later in the document and wins any tie. The first
   job is gone — the panel is the box in all three states — and the second is
   not, so the element selector stays. Drop it and every one of these silently
   stops applying. */
/* THE ORBIT IS SIZED, NOT JUST BOUNDED. Its --size is the diameter of the
   ring and everything on it is placed from that; width alone leaves the
   circle drawn to the window's measure inside a box that is 300px narrower, so
   the arc runs off to the right and the icons drift off it.

   IT NOW MEASURES THE COLUMN IT SITS IN, NOT THE WINDOW MINUS THE PARTS WE
   REMEMBERED. The rail arithmetic — 100vw less --rail-track, less the shell's
   gutters, less the well's own margin — named three of the subtractions the
   shell makes and missed the rest: at a 1316px window with the rail shut it
   computed 1220px of ring inside a hero that measures 880, and .orbit's
   `margin: 32px auto 0` cannot centre a box wider than its parent — both auto
   margins resolve to zero, the circle hangs off the right and its centre sat
   170px right of the page's. Measured in the browser, not read off the sheet.

   THE BOX IT MEASURES IS THE PANEL, AND THE RING TAKES ALL OF IT. The circle
   is the page's horizon, not an illustration inside the reading column, so it
   spans the panel edge to edge: --size is the panel's own width, and the well
   breaks out of the 880px hero to match.

   The container is .landing, not the hero and not .page. Not the hero, because
   container units read the NEAREST query container and there can only be one
   answer — a container on the hero would pin every cq unit here to its 880 and
   leave no way to ask the panel. Not .page, because `container-type` makes an
   element the containing block for its fixed descendants, and the one fixed
   thing in the panel is the guided tour's bar: it would have stopped being
   fixed to the window and started scrolling with the story. .landing is the
   full width of the panel's column, holds the well, and holds nothing fixed.
   :has() keeps it to the page that has an orbit — /products wears .landing too.

   100cqw is a container's CONTENT box, so the panel's own inline padding is
   added back through --flow-gutter, which .page--flow declares beside the
   padding it spends it on (components.css) — the phone drops it to 24px there
   and this follows with no second breakpoint of its own.

   A percentage cannot do any of this: --size is also spent on the well's
   HEIGHT, where a percentage resolves against the parent's height — the one
   number this must not depend on. Container units have no such second meaning.

   The width and the margins are written here, not in the group rule below,
   which is later in the file and would otherwise put the well back inside the
   hero it has just left. */
body .landing:has(.orbit-well) {
  container-type: inline-size;
}

body .orbit-well {
  /* the panel: the column plus the padding the shell holds it in by */
  --panel-w: calc(100cqw + 2 * var(--flow-gutter, 0px));
  --size: var(--panel-w);
  width: var(--panel-w);
  /* out of the hero and onto the panel's two edges */
  margin-inline: calc((var(--panel-w) - 100%) / -2);
}

/* .nix-login-orb is on this list for the reason the rest of it exists: it is
   sized `min(360px, 34vw)` beside a 360px card with 72px between them, and
   34vw is the WINDOW. With the rail out at a 1000px window that is 772px of
   content in a 716px panel — it overflows and the panel's own radius clips
   it. Inside the panel it takes the panel. */
body #wrapwrap.nav-shell > main.page > .nix-login-orb {
  width: min(360px, 26vw);
}

body .ribbon,
body .ab-shotwrap,
body .ab-reel,
body .ab-start,
body .ab-band,
body .ab-bento {
  width: 100%;
  margin-inline: 0;
}

/* The mock itself, which is min(1360px, 96vw) — the 96vw is the same window.
   The 1360 stays: it is a ceiling on how big the picture should ever get, not
   a measurement of anything.

   The gutter is spent here rather than left to 96vw's four per cent, so the
   mock keeps the same 24px a side that .ab-band and .ab-bento already use and
   the page has one edge instead of two.

   (This comment was unterminated for four commits: the three lines above sat
   outside it as bare text, which is a parse error at that point in the sheet —
   everything until the next closing marker was skipped, and the .ab-shot rule
   under it never applied.) */
body .ab-shot { width: min(1360px, calc(100% - 2 * var(--space-3))); }

/* THE BLEEDING BANDS HAVE TO STOP BLEEDING. `margin-inline: calc(50% - 50vw)`
   reaches to the window's edges, and the window is no longer what this box is
   in — with a rail beside it the band would hang off the left of the panel and
   out from under its rounded corner. Inside the panel they simply span it. */
body .rel,
body .writing,
body .talk,
body .index-foot {
  margin-inline: 0;
  /* Was --space-4. The panel already has a gutter of its own between itself
     and the rail, so 32 more inside it spent a third of a card's width on air
     twice over — and the row of cards, which is the widest thing on the page,
     paid for it in the only direction it has to give. */
  padding-inline: var(--space-2);
}

/* .writing was missing from that list and it showed: its own
   `max(calc(50vw - 590px), 4vw)` padding is measured against the WINDOW, so
   with a 268px rail beside it the lede started to the left of the panel and
   the first words of the heading were cut off by the panel's edge. */
body .index-foot { border-radius: 0 0 var(--radius-xl) var(--radius-xl); }

/* NO GROUND OF ITS OWN INSIDE THE PANEL. The foot paints --nix-paper, the
   ground the page stands on when the page IS the window. In the rail layout
   the panel is the ground and paper over it is a darker slab across the bottom
   of it. Transparent, so the panel runs unbroken from the hero to the last
   line of the foot. The releases and the writing need no such rule — they
   carry no ground in either state. */
body .index-foot { background: transparent; }

/* Registered so the two can be TRANSITIONED. An unregistered custom property
   is a string to the animation engine and jumps from one value to the other;
   with a syntax and an initial value it is a length, and the fade grows in
   rather than snapping on the first pixel of scroll. */
@property --fade-l { syntax: "<length>"; inherits: false; initial-value: 0px; }

@property --fade-r { syntax: "<length>"; inherits: false; initial-value: 0px; }

/* THE ROW SCROLLS RATHER THAN SHRINKING. auto-fit would reflow three cards
   into two and then one, which fits by making the row a column — the cards
   are portraits with a picture at the top and they stop being that below
   about 300. Three tracks with a real floor overflow instead, and the row
   takes a sideways scroll of its own; the page underneath is unaffected. */
body .rel .index__grid {
  grid-template-columns: repeat(3, minmax(300px, 1fr));
  overflow-x: auto;
  overflow-y: hidden;
  /* THE TWO LINES THAT MAKE THE SCROLL A SCROLL. Without them the row was a
     900px-wide box in a narrower panel and it simply hung out the right side,
     scrollbar and all.

     width: the base rule is min(1180px, 92vw) and 92vw is the WINDOW — with a
     rail taking 268 of it that is already wider than the panel. 100% is the
     panel.

     min-width: 0 because this grid is an item of the .rel flex column, and a
     flex item's automatic minimum size is its content — the box grows to the
     three tracks rather than clipping them, which is exactly the growth
     overflow-x was asked to absorb. */
  width: 100%;
  min-width: 0;
  /* ROOM FOR THE SHADOWS INSIDE THE SCROLLER. overflow-x: auto makes this box
     clip on BOTH axes — overflow-y computes to auto whatever it is set to — so
     the cards' shadow, which reaches 36px below them and 14 above, was sliced
     off flat at the top and bottom of the row. The padding gives it somewhere
     to fall and the negative margin gives the space back to the layout, so
     nothing moves and nothing is cut. */
  padding-block: 26px;
  margin-block: -26px;
  /* The same trade sideways, at exactly the band's own inline padding: the
     first card's shadow has 16px to fall into on the left, the last one has
     16 on the right, and the row still spans the panel edge to edge. */
  padding-inline: var(--space-2);
  margin-inline: calc(var(--space-2) * -1);
  /* the row is a rail of its own: a card lands under the left edge rather than
     stopping half off it */
  scroll-snap-type: x proximity;
  /* NO SCROLLBAR. A row of cards that scrolls sideways says so by what it
     shows — a card cut by a soft edge — not by a grey bar under it. The bar
     also sat on the panel's ground rather than the row's, so it read as
     furniture belonging to the page. Both engines need telling separately. */
  scrollbar-width: none;
  /* THE CUT EDGE BECOMES A FADE. --fade-l and --fade-r are set by the script
     at the foot of index.xml, and each is the DISTANCE scrolled on its side
     capped at 64 — zero at an end, sixty-four once there is that much hidden
     behind the edge. Not a threshold: a threshold spends the whole fade the
     instant it is crossed, and this row does not rest at exactly zero, so the
     first card sat half-dissolved on a page nobody had scrolled.

     Four stops, not two: transparent at the very edge, opaque by --fade-l,
     opaque until --fade-r from the right, transparent at the right edge. With
     both vars at 0 the whole mask is opaque and the row is untouched. */
  --fade-l: 0px;
  --fade-r: 0px;
  -webkit-mask-image: linear-gradient(to right,
    transparent 0, #000 var(--fade-l),
    #000 calc(100% - var(--fade-r)), transparent 100%);
  mask-image: linear-gradient(to right,
    transparent 0, #000 var(--fade-l),
    #000 calc(100% - var(--fade-r)), transparent 100%);
  transition: --fade-l 200ms linear, --fade-r 200ms linear;
}

body .rel .index__grid::-webkit-scrollbar { width: 0; height: 0; display: none; }

body .rel .index__grid > * { scroll-snap-align: start; }

/* THE WRITING FITS OR IT STACKS, and it never scrolls sideways. The `max-width:
   900px` query that stacks it is measured on the WINDOW, and the box it has to
   fit is the panel — the window less a 268px rail. At a 1000px window the query
   has not fired and the two columns are asked to hold a 16ch statement and a
   list of full post titles in ~350px each, which is what pushed the row wider
   than the panel. auto-fit with a floor answers to the box instead: two columns
   while both can have 260, one the moment they cannot. */
body .writing {
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}

/* The open page's column is min(1180px, 92vw), and 92vw is still the WINDOW.
   With a rail taking 268 of it that column is wider than the panel holding it,
   so the content would run out the right-hand side. Inside the panel it takes
   the panel. */
body.is-nav.is-open .index { grid-template-columns: minmax(0, 1fr); }

/* Pinned to the shell rather than to .index, which is the scroller: inside
   that box a control would scroll away from the reader at the bottom, who is
   exactly the one who needs it. */
/* AND AT ID WEIGHT FOR THE PORTAL LEG. The handle is absolutely positioned
   against this box now, so "is it a containing block" stopped being a detail:
   #wrapwrap is styled by Odoo's own frontend bundle, which is linked after
   this file, and a class cannot outrank an id — if anything there ever sets
   it static, the handle resolves against the viewport instead and lands
   somewhere else entirely on the blog and the login. */
.nav-shell,
#wrapwrap.nav-shell { position: relative; }

/* ── THE TWO CONTROLS DIVIDE THE STATES BETWEEN THEM ──────────────────────
   The seam and the ring were two answers to one question — how wide is the
   navigation — and having both was the bug. They answer different questions
   now, and which one is on the page says which question there is to ask.

   WHILE THERE IS A RAIL, IT IS THE SEAM. Drag to size it, past the floor to
   collapse it to icons, past that to send it away. A ring beside a rail that
   can be dragged is a second way to do what the reader can already see how
   to do.

   ONCE THE RAIL IS GONE, IT IS THE RING. There is no seam worth showing when
   there is nothing on either side of it to resize — a 3px line at the edge of
   a full-width page is a mark on the background, not a control anybody would
   find. The ring sits at the top-left, where this site's mark sits on every
   page that has one, and pressing it does exactly one thing: brings the panel
   back. It does not size anything and it never appears beside a rail.

   The mobile block below turns it back on at every width, because a phone has
   no seam in any state: the rail covers the page rather than standing beside
   it, .rail-grip is display:none there, and the ring is the only pointer
   affordance that opens the menu — no scrim, no outside click. */
.index__menu { display: none; }

/* :not(.is-nav) on both, for the reason spelt out at --rail-track: the phone's
   ring can leave is-shut on a body that also has is-nav, and a rail that is
   open must show the seam and not the ring. */
body.is-shut:not(.is-nav) .rail-grip { display: none; }

body.is-shut:not(.is-nav) .index__menu {
  display: block;
  position: absolute;
  /* THE ROW THE MARK WOULD BE ON. 8 is the shell's padding, 12 the inset the
     rail gives its logo, 12 half of that 24px badge: the centre lands at 32,
     which is where the eye is already looking for the first thing on the
     page. Less half the ring's own 20px box. */
  top: calc(var(--shell-gap) + var(--shell-pad) + var(--rail-logo) / 2 - 10px);
  left: calc(var(--shell-gap) + var(--shell-pad) + var(--rail-logo) / 2 - 10px);
  right: auto;
  width: 20px;
  height: 20px;
  transform: none;
  /* Over the panel it is drawn on, and over the handle's own layer. */
  z-index: 4;
}

/* THE REACH IS A BOX, NOT A BIGGER CIRCLE — the same trade the theme dot
   makes: 20px of drawing is under the 24 a touch target is asked for, and
   growing the circle to reach it is what puts the ring out of scale with the
   mark it stands in for. */
body.is-shut:not(.is-nav) .index__menu::after {
  content: "";
  position: absolute;
  inset: -5px;
  border-radius: 50%;
}

/* ── The phone: THE RAIL COVERS INSTEAD OF PUSHING ────────────────────────
   Two columns of 260 and whatever is left is not a room, it is a corridor —
   at 390px wide the page panel would be 110. So the shell keeps its single
   column and the rail goes over the page, which is what a phone menu is
   anyway.

   The panel gives up its ground and its corners with the gutter that made
   them read: with nothing beside it, a rounded box inset from the window is
   a card containing the whole site. The bands go back to bleeding for the
   same reason — nothing here is narrower than the window. */
@media (max-width: 720px) {
  /* NO STRIP ON A PHONE, in either state: 64px of icons beside a 390px
     window is a sixth of the screen spent on chrome, and the rail here
     covers the page rather than standing beside it. Both states are one
     column and the rail is off-canvas until it is asked for. */
  /* One selector per shell rather than one per shell PER STATE: the three
     states are a value of --rail-track now, not three grid rules, so there is
     nothing left here to out-specify.

     TWO TRACKS, AND THAT WAS A REAL BUG FOR ONE COMMIT. This said `0 0
     minmax(0, 1fr)` — three, left over from when the handle had a column of
     its own. The panel is grid-column 2, which in a three-track grid is the
     middle ZERO, so every phone got the whole site crushed into whatever
     min-content could not shrink past: about 100px of clipped words down the
     left edge. The desktop was fine, which is why it shipped. */
  .nav-shell,
  #wrapwrap.nav-shell {
    grid-template-columns: 0 minmax(0, 1fr);
    column-gap: 0;
    padding: 0;
  }

  /* THE RAIL TAKES NO COLUMN HERE, IN ANY STATE, so the number that says how
     wide it is has to be zero. It covers the page rather than standing beside
     it — but --rail-track still read 58, and every rule that subtracts the
     rail from the window was subtracting a rail that is not there: the orbit
     came out 58px narrower than the screen it was centred in, and the
     notifications panel opened 58px from an edge with nothing at it.

     All three states are named because a class beats the element on its own,
     and the phone's ring can leave is-nav on the body at this width. */
  body,
  body.is-nav,
  body.is-shut {
    --rail-track: 0px;
    --rail-gap: 0px;
  }

  body:not(.is-nav) .nav-rail { visibility: hidden; }

  /* AND THE RELEASE ROW IS ONE CARD WIDE AGAIN. Three tracks with a 300px
     floor and a sideways scroll is what the row does INSIDE A PANEL, beside a
     rail, where the panel is narrower than the window and the cards cannot all
     fit. That rule used to carry body.is-nav; unscoping it — so that the panel
     would be the box in every state — handed it to the phone as well, where it
     outranks pages/index.css's own `grid-template-columns: 1fr` and turned a
     stack of cards into 900px of row in a 390px window.

     Restated rather than undone: `width: auto` would still be this rule's
     word against that one, and the phone's measure is the one that has to
     win. Everything the sideways scroll needed goes with it — the fades, the
     shadow's room, the snap. */
  body .rel .index__grid {
    grid-template-columns: 1fr;
    width: min(420px, 92vw);
    overflow-x: visible;
    padding-block: 0;
    margin-block: 0;
    padding-inline: 0;
    margin-inline: 0;
    -webkit-mask-image: none;
    mask-image: none;
    scroll-snap-type: none;
  }

  /* AND THE RING COMES BACK, because this is the one width where there is no
     seam to drag: the rail covers the page, so .rail-grip is off below and
     the ring is the only pointer affordance that opens the menu. Restated
     here in full — the desktop rule is `display: none`, and a phone that
     inherits it has a navigation with no way in. */
  .index__menu {
    display: block;
    position: absolute;
    top: 22px;
    left: 25px;
    right: auto;
    width: 20px;
    height: 20px;
    transform: none;
    z-index: 20;
  }

  /* AND IT IS GONE WHILE THE MENU IS OPEN. The shared trigger crosses into an
     X on aria-expanded — right on a page where a full-screen sheet is over
     you and the X is the only thing that says how to leave. This menu has two
     better answers now: pick a destination and it takes you there and shuts
     itself, or tap the scrim beside it. A third control that does what both
     of those already do is a third thing to look at on a 390px screen.

     display, not visibility: it must not hold a 24px hole in the corner of a
     rail that is covering the whole page. */
  body.is-nav .index__menu { display: none; }

  /* NO REACH PSEUDO-ELEMENT ON THIS CONTROL, and that is not a shortcut.
     ::after on .site-menu__trigger is the SECOND STROKE OF ITS CROSS
     (components.css) — the one that makes the ring an X while the menu is
     open. A reach box declared here took it over: inset -5 and a 50% radius
     turned that stroke into a grey half-disc, so the open menu showed one
     diagonal and a blob instead of a cross.

     The desktop version of this rule survived only because it shipped beside
     two more that put the strokes back on aria-expanded; those went with the
     desktop ring, and this was left overriding a glyph with nothing to
     restore it. The trigger brings its own 24px target from components.css,
     which is what a thumb is asked for anyway. */

  /* AND THE SEAM IS NOT DRAGGABLE HERE. The rail covers the page at this
     width instead of standing beside it, so there is no gutter between two
     panels to take hold of — and a 14px column of "resize" under a thumb is
     a scroll that does not scroll. */
  .rail-grip { display: none; }

  /* ── THE SCRIM, AND IT IS ONLY EVER HERE ─────────────────────────────
     At this width the rail COVERS the page instead of standing beside it, and
     a panel laid over a page it does not dim is a panel the eye keeps reading
     through: the release cards went on competing with the four destinations
     over them. Blurred rather than merely darkened, because what is behind is
     photographs and white cards — a flat tint over those reads as a bug in
     the page, and a blur reads as depth.

     A pseudo-element of the shell rather than an element of its own: the
     markup is copied into five templates and a sixth scrim is a sixth place
     to forget one. Fixed, so it covers the window rather than the scroller's
     content box — the panel scrolls, and a scrim that scrolls with it is a
     hole at the bottom of a long page.

     One under the rail: --z-over is the rail's, and the scrim has to sit
     between it and everything else.

     There is NO scrim on a desktop, at any width, in any of the three states.
     There the rail takes a column of its own and dims nothing, because
     nothing is behind it. */
  body.is-nav .nav-shell::before,
  body.is-nav #wrapwrap.nav-shell::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: calc(var(--z-over) - 1);
    background: color-mix(in srgb, var(--nix-paper) 45%, transparent);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
  }

  body.is-nav .nav-rail {
    position: fixed;
    inset: 0 auto 0 0;
    width: min(280px, 86vw);
    z-index: var(--z-over);
    border-radius: 0 var(--radius-xl) var(--radius-xl) 0;
  }
  /* NO PANEL AT THIS WIDTH, IN ANY STATE. The ground and the corners are what
     say "this box has something beside it", and here nothing does — a rounded
     inset box on a 390px window is a card containing the whole site. It was
     scoped to body.is-nav, which was correct while .index only had a ground
     in that state and is a phone with a rounded page in the other two now
     that the panel is unconditional. */
  .index,
  .page { background: none; border-radius: 0; }
  body .index-foot { border-radius: 0; }

  /* THE WAY OUT HAS TO BE ON TOP OF WHAT IT CLOSES. On a desktop the rail
     pushes and the ring travels to its far edge; here the rail COVERS, and
     the ring's desktop offset put it deep inside the rail — inside the rail's own 0-280
     band and underneath it, at z-index 20 against the rail's 60. It is the
     only pointer affordance that closes the menu: no scrim, no outside
     click. A reader who opened it on a phone could not shut it. */
  body.is-nav .index__menu {
    left: calc(min(280px, 86vw) - 20px - var(--shell-pad));
    z-index: calc(var(--z-over) + 1);
  }
}

/* ── Focus and reach ──────────────────────────────────────────────────────
   The rail is the site's navigation and every item in it is a link, so it
   answers a keyboard the same way everything else does.

   AND IT IS 44 TALL. The row was padding plus a 14px line — about 36px, under
   every touch minimum there is, on the control a phone reader uses most. The
   type and the padding are unchanged; the row simply stops being shorter than
   a thumb. */
.nav-rail__item,
.nav-rail__login {
  min-height: 44px;
}

.nav-rail__item {
  box-sizing: border-box;
}

/* OUTSET, and that is not a preference. The ring is drawn in the accent,
   which is the ink — and --nix-ink is exactly the ground of .nav-rail__login
   and of the current item. Inset, it was black on black in light and white on
   white in dark: a focus state that is invisible on the two controls a
   keyboard reaches first. Outside the box it lands on the rail. */
.nav-rail__item:focus-visible,
.nav-rail__login:focus-visible,
.nav-rail__brand:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
}

/* AND THE TOOLS AT ITS FOOT TAKE THEIR REACH FROM components.css, which is
   where the controls themselves are drawn — the rule that stood here sized
   them from the shell, which is four of the seven pages that carry them. */

/* ── THE MENU'S OWN SEARCH ─────────────────────────────────────────────────
   A field between two rules at the head of every rail this site draws. It
   started in portal.css, on the argument that only the account had one; the
   store's five shelves and the site's four places behind three levels are as
   worth searching, so the field is shared markup now (views/partials/rail.xml)
   and its rules belong beside the rest of the rail.

   The box is the same seat a row is: same radius, same 12px inset, same quiet
   ink, so the field reads as part of the column instead of a control dropped
   into it. */
.nav-rail__find {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--nix-text) 5%, transparent);
  color: var(--nix-text-soft);
}

.nav-rail__find svg {
  flex: none;
  width: 18px;
  height: 18px;
}

.nav-rail__find-input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  color: var(--nix-text);
  font: inherit;
  font-size: 14px;
}

.nav-rail__find-input::placeholder { color: var(--nix-text-soft); }

.nav-rail__find-input:focus { outline: none; }

.nav-rail__find:focus-within {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* NOT IN THE STRIP. Collapsed, the rail is a column of 20px glyphs — there is
   no room for a field, and a magnifier that widened the menu when pressed
   would be a second control doing the ring's job. The rule under it goes too:
   two rules with nothing between them is a drawn line for its own sake. */
body:not(.is-nav) .nav-rail__find,
body:not(.is-nav) .nav-rail__find + .nav-rail__sep {
  display: none;
}

/* WHILE IT IS FILTERING, EVERY LEVEL IS ON THE PAGE. The rail's own rules hide
   the levels nobody asked for, and one of them is written with :has(#id) — an
   id, which no selector made of classes can outrank. This is the one place
   where what is displayed is not "the level you drilled into" but "the rows
   that matched", wherever they live. */
.nav-rail.is-finding .nav-rail__nav {
  display: flex !important;
}

/* A level's own furniture is not a result: Back describes the way out of a
   list, and neither is a place. */
.nav-rail.is-finding .nav-rail__back {
  display: none;
}

/* A LEVEL'S OWN CAPTION IS NOT A RESULT HEADING. The public rails name their
   first list in a caption OUTSIDE the <nav> — "Menu", "Marketplace" — which
   while a search is running would sit above whatever the first matching group
   happens to be. The one inside each nav takes over. */
.nav-rail.is-finding .nav-rail__caption:not(.nav-rail__find-cap) {
  display: none;
}

/* THE CAPTION OVER EACH GROUP OF RESULTS. Searching four levels at once means
   two rows can carry nearly the same word — Marketplace the store, Marketplace
   settings the publishing page — so each group says which list it came from.
   Drawn only while finding, and only over a group that found something;
   js/rail.js owns the hidden attribute on both. */
.nav-rail.is-finding .nav-rail__find-cap {
  display: block;
}

/* A level nothing matched is not drawn: .is-finding shows every list, so
   without this an empty <nav> still takes its gap between two groups. The
   !important pairs with the one above it — same :has(#id) rule being answered. */
.nav-rail.is-finding .nav-rail__nav.is-empty {
  display: none !important;
}

/* AND NOTHING IS "CURRENT" IN A LIST OF RESULTS. Every level marks its own row
   for the page you are on, so a search that draws four levels at once drew up
   to four filled seats — reported as rows lighting up for no reason anybody
   gave. The page you are standing on is still there, just not shouting. */
.nav-rail.is-finding .nav-rail__item.is-on {
  background: none;
  color: var(--nix-text-soft);
}

/* .nav-rail__item is display:flex, which beats the browser's own [hidden]
   rule — so the attribute has to be spelt out here or nothing ever hides. */
.nav-rail__item[hidden],
.nav-rail__nav[hidden] {
  display: none;
}

/* The empty state is a statement, not a destination: no hover, no pointer,
   no ink of a row you can press.

   IT USED TO SAY "the same quiet as the Nothing here yet row one level
   down". That row was the Business Services level before it had pages, and
   it is gone — the level opens five lists now. A comparison to something
   deleted describes nothing, so the quiet is stated on its own terms. */
.nav-rail__none {
  color: var(--nix-text-quiet);
  cursor: default;
}
