/* Application shell -- MainLayout.razor and NavRail.razor render into this.
   Source of truth: docs/design/ui/shell/2026-09-09-shell-mockup.html, transcribed verbatim.

   TOKEN OWNERSHIP:

   tokens.css declares the base palette (--bg, --pane, --rail, --ink, --ink2, --ink3,
   --line, --line2, --accent, --accent-bg, --accent-line, --ok, --ok-bg, --warn, --warn-bg,
   --stop, --stop-bg, --cool, --cool-bg, --row, and the search screen's --reg-* hues) at
   bare :root, and App.razor links tokens.css on every page (it is the app's single host
   page) before this file, so that palette already applies shell-wide. This file does NOT
   redeclare any of it.

   This file owns exactly the five --nav-* chrome tokens the mockup adds on top of that
   palette for the rail -- --nav, --nav-ink, --nav-ink-strong, --nav-hover, --nav-active --
   copied verbatim from the mockup's own TOKENS block. If a token value ever needs to change,
   there is exactly one place each one lives: tokens.css for the base palette, this file for
   --nav-*. Declared at bare :root first, then redefined under the dark media query guarded as
   :root:not([data-theme="light"]), then again under :root[data-theme="dark"] -- so an
   explicit light/dark choice always wins over the OS setting, and nothing here is defined
   only inside a dark block. */
:root {
  --nav: #152D4D; --nav-ink: #B9CAE1; --nav-ink-strong: #FFFFFF;
  --nav-hover: #1E3E68; --nav-active: #2B5C96;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --nav: #101215; --nav-ink: #9BA1AA; --nav-ink-strong: #E6E8EC;
    --nav-hover: #1B1E22; --nav-active: #22303F;
  }
}
:root[data-theme="dark"] {
  --nav: #101215; --nav-ink: #9BA1AA; --nav-ink-strong: #E6E8EC;
  --nav-hover: #1B1E22; --nav-active: #22303F;
}

/* ---------------------------------------------------------------------------------------------
   Shell grid -- rail on the left, main column (top bar + page body) filling the rest.
--------------------------------------------------------------------------------------------- */
.shell {
  display: flex;
  min-height: 100vh;
  background: var(--bg);
  /* The shell-wide text colour. Neither the rest of this file nor search.css (whose own header
     comment says its rules are scoped to .search-shell on purpose) sets one, so without this line
     shell chrome outside .search-shell -- the top bar's own text, for one -- takes
     blazing-berry.bs5.min.css's Bootstrap default (#212529, near-black) regardless of --ink. That
     default turns light only under App.razor's blazing-dark.bs5.min.css, which follows its link's
     `media` (set by App.razor's window.team365ApplyTheme), not `data-theme`; wherever the two
     disagree (team365ApplyTheme leaves the sheet unsynced if its link is missing) that is #212529 text on the
     dark --pane background .shell-topbar sets -- unreadable, and invisible in the markup. With
     this line the whole shell inherits a theme-aware colour the same way --bg already does,
     rather than only its DevExpress-brand children doing so. */
  color: var(--ink);
}

.shell-main {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  background: var(--bg);
}

.shell-topbar {
  /* Height matches the mockup's 48px .topbar. */
  flex: none;
  min-height: 48px;
  background: var(--pane);
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
}

/* NO `overflow` of its own, and that is load-bearing rather than an omission -- see the footer
   action bar block below for the whole argument. In one line: `overflow: auto` makes an element
   a scroll container whether or not its content ever overflows, and the routed page's sticky
   footer lives INSIDE here, so `overflow: auto` would make .shell-body (not the viewport)
   the scrollport sticky positions against. .shell-body never actually scrolls -- nothing
   constrains .shell-main's height, so it grows to fit its content and the DOCUMENT is what
   scrolls (measured: scrollHeight === clientHeight, and again by injecting 2000px of content
   live) -- so a sticky bar inside a non-scrolling scrollport would simply sit at the bottom of
   the grown document, below the fold.

   Leaving it off costs nothing else. .shell-body is a flex ITEM of .shell-main, and a flex item
   already establishes an independent formatting context, so the block-formatting-context side
   effect `overflow: auto` also carried (containing floats, blocking margin collapse -- what
   keeps .search-page's own `margin: 14px 0` and .shell-placeholder's `3rem auto` inside the
   body instead of escaping it) is still in force without it.

   `display: flex; flex-direction: column` is the top of the chain that lets a page-owned footer
   be pushed to the bottom on a page SHORTER than the viewport -- see .actionbar-host below. The
   routed page's own root becomes a flex item; a page that does not opt into .actionbar-host gets
   the default `flex: 0 1 auto`, i.e. its content height, top-aligned -- which is NOT
   exactly block layout: centring still holds, because
   horizontal `margin: auto` centres a flex item exactly as it would a block one, but FILLING
   does not -- a flex item sized `flex: 0 1 auto` shrinks to fit-content instead of stretching
   to the row's full width the way a block-level child with `width: auto` would.
   `.shell-placeholder` sets its own `max-width` and centres, so the difference does not show
   there; search.css's `.search-page` fills because its horizontal margin is `0`, not `auto`
   (see that rule's own comment). */
.shell-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* ---------------------------------------------------------------------------------------------
   Top bar contents -- TopBar.razor's scope regions (.topbar-scopes and
   .topbar-farright, below; see TopBar.razor's own doc comment for the grouping),
   and the icon-first Profile menu. Source: 2026-09-09-shell-mockup.html's own
   .crumb/.scopes/.iconbtn/.avatar rules, renamed to this file's topbar-* prefix rather than
   copied verbatim -- NavRail.razor already established that convention over the mockup's own
   bare class names (nav-rail__item vs. navitem), so this keeps one naming style across the
   shell instead of mixing two. DxToolbar/DxToolbarItem supply their own button chrome
   (dxbl-*, internal and not overridden here -- see App.razor's own comment on why); these
   rules only size the custom icon markup TopBar.razor puts inside each item's ChildContent.
--------------------------------------------------------------------------------------------- */
/* DxToolbar's own title box (`.dxbl-toolbar-title`, the element TitleTemplate renders into)
   defaults to `flex-grow: 0` -- sized to its own content, with a separate flexible spacer
   DxToolbar renders after it absorbing the rest of the row's width so the right-aligned Items
   (the environment chip and the account menu) land at the true right edge. `.topbar-farright`'s own `margin-left: auto`
   needs THIS box to actually be as wide as the row, not just its own content, to have anywhere to
   push against -- `flex: 1 1 auto` takes over the spacer's job. `min-width: 0` is required
   alongside `flex-grow` on a flex item for it to be allowed to shrink below its content's natural
   width at all -- without it, the wrap (its own `flex-wrap`, every width since item 3514) cannot do
   its job either, since a flex item's default `min-width: auto` refuses to shrink past its own
   content. */
.dxbl-toolbar-title {
  flex: 1 1 auto;
  min-width: 0;
  flex-wrap: wrap;
}
/* DxToolbar renders its OWN flexible spacer (class dxbl-toolbar-placeholder, confirmed by reading the live DOM) between
   the title box and the right-aligned items, to push those items to the true right edge. Item 3596 (R4, R11) takes it
   out of layout at every width: `display: none` rather than the `flex: 0 0 0 !important` it had since 2026-09-20, so
   that when the right-aligned group wraps onto a row of its own no spacer stands ahead of it, and the group's own
   `margin-left: auto` (next rule) puts the TEST chip and the account button at the right end of whichever row they
   land on. `.dxbl-toolbar-title`'s `flex: 1 1 auto` (above) still claims the rest of a shared row, so
   `.topbar-farright`'s `margin-left: auto` still reaches the chip. The selector spells DevExpress's own chain under
   `.shell-topbar` (four classes) because DevExpress 26.1.6's `.dxbl-toolbar.dxbl-toolbar-adaptive>.dxbl-btn-toolbar>div`
   (three classes and an element, `display: inline-flex`) reaches the spacer as one of its `> div` children and outranked
   the two-class `.shell-topbar .dxbl-toolbar-placeholder`: the spacer stayed in layout, 269px wide at 1440px
   (docs/reviews/3596/browser-layout.md, F1). Four classes win on specificity, so no `!important` is needed (R11). */
.shell-topbar .dxbl-toolbar > .dxbl-btn-toolbar > .dxbl-toolbar-placeholder {
  display: none;
}
/* Item 3596 (R2-R4, R11; spec C2): the right-aligned group -- the environment item and the account item, siblings in
   DxToolbar's one `.dxbl-adaptive-group` (docs/superpowers/briefs/3596/2026-10-30-probe.md, Q1) -- keeps to the right
   end of its row at every width, not only inside a width block, so a wrap at any width keeps the pair at the right.
   No `!important`: DevExpress 26.1.6 sets this group's `align-self` and `display`, never its margin. */
.shell-topbar .dxbl-adaptive-group {
  margin-left: auto;
  /* The product owner, 2026-10-30 (PR 5891): 8px between the TEST chip and the account button, the same as between
     the two scope pickers (.topbar-farright's gap); DevExpress 26.1.6 puts the two flush (0px). */
  gap: 8px;
}
/* Item 3596 AC2: the 24px chip is centred on the account button. DevExpress 26.1.6 makes every root item
   `inline-flex` and stretches it to the row (`.dxbl-toolbar.dxbl-toolbar-adaptive>.dxbl-btn-toolbar>div>
   .dxbl-toolbar-item`), with no `align-items`, so the template's wrapper stretched and the chip sat at its top:
   5px above the button's centre at 1440 and 1280px, 3px at 820 and 390px (probe Q2). Centring the item's own content
   fixes that. `:has()` picks the item by the chip it holds, as `.shell:has(...)` below already does (SD-57).
   This widens R11's list of named DevExpress boxes by one, that root item box, and stays layout-only (`align-items`):
   the browser run measured `centreOffset` 0.0 in 27 of 27 rows (docs/reviews/3596/browser-layout.md). */
.shell-topbar .dxbl-adaptive-group > :has(.topbar-envchip) {
  align-items: center;
  /* The product owner, 2026-10-30 (PR 5891): the chip keeps the 8px the two scope pickers have between them
     (.topbar-farright's gap) from the Association picker. Without this margin the gap measured 7px; with it, 8.00px
     at 1912, 1700, 1600, 1536, 1440, 1366 and 1280px (below that the chip wraps onto its own row). Pinned by
     ShellCssTopBarTests.The_environment_chip_keeps_8px_from_the_association_picker_and_the_account_button. */
  margin-left: 1px;
}

/* The inline `min-width` DxToolbar's own script writes onto <dxbl-toolbar> (and its inner
   .dxbl-btn-toolbar sizes to) is dropped at EVERY width, not only below 1200px (the media block
   further down keeps its other resets). Why it matters now: the account avatar is never hidden
   (TopBar.razor's AdaptivityAutoHideRootItems="false", the product owner's ruling of 2026-10-26 that
   no "..." button ever renders), and that script measures TitleTemplate at its natural width -- two
   scope pickers it substitutes a ~635px placeholder for each (TopBar.razor's own markup comment) --
   then writes title + the right-aligned items (the env chip and the avatar, item 3596 R3) as
   min-width. Measured live at 1440px (rail collapsed, shell 1384px wide): min-width 1397px, the
   toolbar overran the shell by 13px
   (documentElement.scrollWidth 1453 against clientWidth 1440) and the avatar sat half off-screen at
   x=1401. Before, auto-hide moved the avatar into the "..." menu whenever the sum did not fit,
   which is what kept the bar inside the viewport. With min-width 0 the title box (flex: 1 1 auto,
   min-width: 0, above) takes whatever the avatar leaves, .topbar-farright's real ~620px fits, and
   scrollWidth equals clientWidth. Inline `min-width` loses to `!important` regardless of specificity,
   the same wall the 1200px block below explains. Since item 3514 (SD-3559 R4, R14) the wrap applies
   at every width here too, so content-sized pickers drop to a second row rather than push the bar
   past the viewport above 1200px; the 1200px block keeps only what is width-specific. */
.shell-topbar .dxbl-toolbar,
.shell-topbar .dxbl-btn-toolbar:not(.dxbl-virtual-toolbar) {
  flex-wrap: wrap;
  min-width: 0 !important;
}

/* The named region at the start of the bar (TopBar.razor's .topbar-scopes). Empty since the product
   owner removed the Type chip, and the crumb label before it, on 2026-10-26 (item 3458); SD-94 decision 9 puts Event and
   Association in .topbar-farright below instead, a deliberate regrouping, not drift (see
   TopBar.razor's own doc comment). */
.topbar-scopes {
  display: flex;
  gap: 8px;
  margin-left: 6px;
  min-width: 0;
}

/* The scope pickers (AssociationScopePicker.razor, EventScopePicker.razor). Chip look from the
   approved search mockup's .tb-chip and .tb-chip .k (docs/design/ui/search/2026-10-22-search-redesign-mockup.html
   at d6d686201, lines 202-203; item 3560 spec R8): a 7px gap, the label at 11px, weight 600, sentence case, no
   letter-spacing. The chip keeps 32px, not the mockup's 30px: at 30 DevExpress's 29.99px combo overlaps the chip's
   1px border (item 3560 spec C9). The look before item 3560 (9px, weight 700, uppercase) was borrowed from the
   unapproved 2026-09-09 shell mockup's .scope button. Colours -- accent-bg/accent-line/accent -- are tokens.css's
   (this file's own token-ownership comment above), so no new token is needed here, and they follow the theme.
   Hover keeps DevExpress's own look (item 3560 R13). Unlike the mockup's inert button this wraps a real, functional
   DxComboBox, so the chip is sized around it rather than the other way round. No positioning
   that assumes it is first or only -- .topbar-farright below supplies the gap between this and
   the chip beside it. The same markup and classes are reused, disabled, for the
   "options unavailable" state a failed roster fetch renders (AssociationScopePicker.razor's own
   header comment) -- no second visual register for a chrome element every page carries. */
.topbar-scope-picker {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 9px;
  background: var(--accent-bg);
  border: 1px solid var(--accent-line);
  border-radius: 6px;
}
.topbar-scope-picker > label {
  font-size: 11px;
  font-weight: 600;
  text-transform: none;
  color: var(--accent);
  white-space: nowrap;
}
.topbar-scope-combo {
  /* DxToolbar's off-screen adaptive-measurement clone (class dxbl-virtual-toolbar) does not
     measure this element's real content AT ALL, so no width assigned here caps what it measures.
     Read from the live DOM (2026-09-20): the clone substitutes a bare, class-less placeholder div
     for the whole picker (`.topbar-scope-picker`'s own class survives on the clone; the combo box
     inside it does not), carrying a fixed inline width (~635px) IDENTICAL across the Event and
     Association pickers despite their different label text and roster content -- proof it is not
     derived from this element's CSS or content at all. What keeps the pickers out of that
     measurement -- they sit in TitleTemplate, which this measurement pass does not apply to,
     rather than in a DxToolbarItem's ChildContent -- is in TopBar.razor's own topbar-farright
     markup comment; see that comment for the citation.

     The width rules below are for real (non-virtual) rendering. SD-3446 R25, SD-3559 R3 (item
     3514): the box sizes to the selected name (its input's `field-sizing: content`, below), at
     least 80px and at most 32vw -- 26vw at 1280px and below -- so an event's name and year are
     not cut; beyond the cap the ellipsis rule below truncates it. The mockup's `.tb-scope select`
     carries the same three numbers. */
  width: auto;
  min-width: 80px;
  max-width: 32vw;
}
/* Truncation for a box capped at 32vw (26vw at 1280px and below) showing a value of unpredictable
   length. Without it, a selected scope name longer than the cap would clip or wrap unpredictably,
   because the box cannot grow past it. DxComboBox renders its
   display text on an inner <input>, which does not inherit text-overflow/white-space/overflow
   from its containing element, so the rule targets the input directly; the rule on
   .topbar-scope-combo itself is kept alongside it in case CssClass output changes. */
.topbar-scope-combo,
.topbar-scope-combo input {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* SD-3559 R3, R14 (item 3514; first-plan gate row G11): the input takes its text's rendered width, not
   a character estimate, so the box above grows and shrinks with the selected name between its 80px
   floor and its cap. The mockup's .tb-scope select carries the same field-sizing. `min-width: 0`
   (item 3514 late ruling LR3, docs/superpowers/briefs/3514/late-rulings.md): a flex item's automatic minimum is
   its content width under field-sizing, so without it the input never shrinks at the cap, the
   drop-down button is pushed past the combo's `overflow: hidden` edge and clipped, and the ellipsis
   never shows (cause inferred from the before/after measurements, not isolated; docs/reviews/3514/
   t3-pickers.md Addenda 1 and 2); with it the input shrinks to the capped box and the button stays
   inside. */
.topbar-scope-combo input {
  field-sizing: content;
  min-width: 0;
}

/* Item 3560 R9, R10 (approved search mockup at d6d686201, lines 194, 209-212, 545): the locked event chip.
   EventScopePicker.razor adds is-locked to the chip and renders the padlock span only while IEventScope.Locked, so
   neither rule needs a display toggle; the gated and failed-roster chips never get either (R13). The locked look is a
   cursor only. DevExpress's own disabled rule (.dxbl-text-edit.dxbl-disabled > * { opacity:
   var(--dxbl-text-edit-disabled-opacity) }) already dims the combo's children to 0.6, and the mockup's opacity: .55
   on top would stack to 0.33 (docs/superpowers/briefs/3560/measure.md B, D) -- so no scope rule sets an opacity, and
   none targets a dxbl-* class or a --dxbl-* variable (R11; this block's header comment). The cursor is set on the
   input as well as the combo because the disabled input's own cursor: default otherwise wins over its text area
   (measure.md D; spec C11), and on the disabled drop-down button too (late ruling LR4: the mockup's locked native
   select takes the cursor over its whole box, arrow included, line 209; :disabled outranks DevExpress's own
   cursor: default on the button without naming a dxbl-* class). The padlock's svg takes the mockup's .tb svg stroke rule at 13px, as a block so the span
   is the glyph's 13px high (late ruling LR3, from the mockup's general svg rule, line 194); currentColor takes
   --ink3 from the span. The class is topbar-scope-lock, never a bare .lock: the company and person overview mockups
   use .lock for an unrelated badge (spec 1.4). */
.topbar-scope-picker.is-locked .topbar-scope-combo,
.topbar-scope-picker.is-locked .topbar-scope-combo input,
.topbar-scope-picker.is-locked .topbar-scope-combo button:disabled {
  cursor: not-allowed;
}
.topbar-scope-lock {
  color: var(--ink3);
}
.topbar-scope-lock svg {
  display: block;
  width: 13px;
  height: 13px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The scope-picker group (SD-94 decision 9). It lives in TitleTemplate, not its own
   right-aligned DxToolbarItem: live testing (2026-09-20) found that DxToolbarItem placement
   collapses both pickers into DxToolbar's "..." overflow menu at every normal desktop width (see
   .topbar-scope-combo's own comment above, and TopBar.razor's markup comment, for the full
   citation). TitleTemplate content is not right-aligned by DxToolbar itself -- its own box
   (`.dxbl-toolbar-title`) sizes to its content and sits left of a flexible spacer DxToolbar
   renders to push the right-aligned Items (the environment chip and the account menu) to the true right edge.
   `margin-left: auto` here, combined with `.dxbl-toolbar-title`'s own `flex: 1 1 auto` override
   above (stealing that spacer's job), pushes this group to the right edge of the expanded title
   box instead -- visually adjacent to the account menu, giving the group its "far right"
   placement, while staying outside DxToolbarItem's collapse-into-overflow mechanism entirely.
   Product owner requirement, 2026-09-20: this group (both pickers) must always be
   visible -- wrap to a second line rather than hide, never collapse into a menu; its own
   `flex-wrap` (every width, SD-3559 R14) and the 1200px escape hatch a few blocks down deliver the
   wrap when there truly isn't room. */
.topbar-farright {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  margin-left: auto;
  flex-wrap: wrap;
}

/* The environment chip (SD-94 decision 10). Copied from the mockup's
   own .envchip/.dot rules verbatim (2026-09-09-shell-mockup.html's .scopes block) -- --ok/--ok-bg
   are already declared by tokens.css (this file's own token-ownership comment above),
   so no new token is introduced. */
.topbar-envchip {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 24px;
  padding: 0 9px;
  border-radius: 12px;
  background: var(--ok-bg);
  color: var(--ok);
  border: 1px solid var(--ok);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  white-space: nowrap;
}
.topbar-envdot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ok);
}
/* Item 3596 R10: the environment chip's visually hidden "Environment: ", which names the toolbar's arrow-key stop on
   the chip while only the label shows. The company-overview mockup's .sr, as company-overview.css's .visually-hidden
   carries it (that rule is scoped to .company-page; Bootstrap's own is not relied on). */
.topbar-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.topbar-avatar {
  width: 16px;
  height: 16px;
  display: grid;
  place-items: center;
  color: inherit;
}
.topbar-avatar svg {
  width: 16px;
  height: 16px;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* SD-3446 R26, SD-3559 R2, R13 (first-plan gate row G8): the "Appearance" heading in the Account menu.
   DxToolbarItem has no heading item (TopBar.razor's markup comment), so it is a disabled item; its
   CssClass lands on the item's own <button class="dxbl-btn ... dxbl-disabled dxbl-dropdown-item
   topbar-menu-head" disabled>. DevExpress 26.1.6 dims it through the button's CHILD span (a 7-class
   rule, opacity .6), so undimming the button itself does nothing; the first rule below needs 8 classes,
   in both the .dxbl-disabled and :disabled forms, to win. Coupled to DevExpress 26.1.6's selector
   shape: re-check on a DevExpress upgrade. The second rule gives it the approved mockup's .menu-head
   look (padding 4px 12px 2px, 11px, 600, --ink3). Its selector repeats DevExpress's own 5-part
   drop-down item prefix because DevExpress 26.1.6 sets that item's padding with a 5-part rule, which a
   shorter selector loses to (item 3514 late ruling LR2, SD-3559 R13). */
.dxbl-toolbar-dropdown .dxbl-toolbar-dropdown-item .dxbl-toolbar-menu-item .dxbl-dropdown-item.dxbl-btn.dxbl-disabled.topbar-menu-head>:not(.dxbl-image),
.dxbl-toolbar-dropdown .dxbl-toolbar-dropdown-item .dxbl-toolbar-menu-item .dxbl-dropdown-item.dxbl-btn:disabled.topbar-menu-head>:not(.dxbl-image) {
  opacity: 1;
}
.dxbl-toolbar-dropdown .dxbl-toolbar-dropdown-item .dxbl-toolbar-menu-item .dxbl-dropdown-item.dxbl-btn.topbar-menu-head {
  padding: 4px 12px 2px;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink3);
  cursor: default;
}

/* SD-3446 R25, SD-3559 R3 (mockup: .tb-scope select { max-width: 26vw } at 1280px and below). */
@media (max-width: 1280px) {
  .topbar-scope-combo {
    max-width: 26vw;
  }
}

/* Narrow-width overflow (n1-topbar-overflow.md) -- the top bar's own, which the action-bar
   block below leaves to this one (that block's own comment: "traced to the top bar's
   own `.topbar-scopes`/`DxToolbar` region overflowing, not to `.actionbar`"). Measured on the
   unpatched page at 375px CSS width: `document.documentElement.scrollWidth` 712 against a
   `clientWidth` of 375, entirely attributable to `dxbl-toolbar.dxbl-toolbar-adaptive` and its
   inner `.dxbl-btn-toolbar`, both 656px wide reaching x=712, plus `.dxbl-toolbar-title` (this
   file's `.topbar-scopes` + `.topbar-farright`, TopBar.razor) at 618px reaching x=674 (measured
   when the title box also held the since-removed crumb label).

   ROOT CAUSE, found by reading the live DOM (`outerHTML`), not by guessing from the theme
   stylesheet: `<dxbl-toolbar ... style="height: 64px; min-width: 770px;">` -- an INLINE style,
   written by DxToolbar's own adaptive-layout JavaScript (the component ships
   `auto-collapse-items-to-icons`/`auto-hide-root-items` attributes and measures its own content
   to decide when to collapse `Items` into the ellipsis menu). That JS measures the TitleTemplate
   content (this project's `.topbar-scopes` + `.topbar-farright`, arbitrary markup DxToolbar's
   adaptive engine does not itself collapse) at its natural, unwrapped width and writes the result
   straight onto the element's `style` attribute. No CSS selector, at any specificity, can
   outrank a plain inline style -- specificity comparisons only apply between stylesheet rules;
   an inline declaration wins over all of them regardless of class count. That is a different
   wall from the action-bar fix's (a theme STYLESHEET rule, beaten there by a higher-specificity
   selector, see that block's comment on `.actionbar .dxbl-pager .dxbl-btn.dxbl-btn-outline-
   secondary`) -- confirmed by testing the equal-or-higher-specificity approach first: giving
   `.shell-topbar .dxbl-toolbar`/`.dxbl-btn-toolbar` `flex-wrap: wrap; min-width: 0; flex: 1 1 0%`
   changed nothing at all (byte-identical measured widths before and after), because the inline
   `min-width: 770px` was still in force underneath every one of those rules.

   THE FIX has three parts. Since item 3514 (SD-3559 R14) the toolbar's wrap, its title's and
   `.topbar-farright`'s wrap, and the `min-width: 0 !important` apply at every width (the base rules
   above); the rest of part one's wrapping and parts two and three (the other `!important` overrides
   and `position: static`) live once in the 1200px block.
   First, shrink what the JS actually measures. History: the combo's width was once dropped from a
   fixed 140px to 96px at 900px; item 3514 removed that (SD-3446 R25), and the pickers now size to the
   selected name, capped at 32vw, 26vw at 1280px and below, so there is no 900px width rule any more.
   What remains of part one is that `.topbar-scopes`/`.topbar-scope-picker` are allowed to wrap so the
   two chips can stack instead of sitting side by side. Second, override the inline `min-width`/
   `width`/`height` with `!important` -- the one CSS mechanism that *can* beat a plain inline
   style, and the correct tool for exactly this situation (an inline style, not a theme rule)
   rather than a second attempt at winning on specificity, which the measurement above already
   showed cannot work here.

   Third, and found only after the first two still left the wrapped chips visually overlapping
   the routed page below: `.dxbl-btn-toolbar` -- the element actually holding the scopes
   and icon buttons -- is `position: absolute` (confirmed by reading the live DOM), the mechanism
   DxToolbar's own resize animation relies on. An absolutely positioned box does not make its
   containing block grow to fit it, so once wrapping made that box taller than the height DxToolbar
   had computed for the UN-wrapped, single-row case, the extra height simply painted over whatever
   came next in the document instead of pushing it down. `position: static !important` here
   returns it to normal flow so `.dxbl-toolbar`'s own `height: auto` (immediately above) can size
   around it the ordinary way. DxToolbar also renders a second, hidden `.dxbl-btn-toolbar` purely to
   measure content off-screen (`dxbl-virtual-toolbar`, `top: -10000px`) -- excluded by
   `:not(.dxbl-virtual-toolbar)` below so this rule does not pull an invisible duplicate of every
   scope chip into flow as well.

   Verified live at 320/375/500/768/820/900/1024/1280px (rn1-topbar-report.md): scrollWidth equals
   clientWidth at every width from 320 up; both chips remain visible, labelled (their <label> text
   is untouched) and legible, never overlapping the icon buttons; the action bar below is
   unaffected (unchanged markup and rules). The 1200px reset below applies at 1024px; above 1200px
   it is inert, as the 1280px measurement of byte-identical toolbar geometry showed (that
   measurement predates the every-width wrap of item 3514).

   THE 900px THRESHOLD IS NOT SAFE with EventScopePicker/AssociationScopePicker in
   TitleTemplate (this file's own .topbar-farright comment above) -- TitleTemplate content is
   exactly what DxToolbar's JS measures to compute the inline min-width (dropped at every width by
   the unconditional .shell-topbar rule above), and they add ~500px to it. Measured live: on
   /dashboard with NavRail expanded (224px, NavRail.razor's own IsExpanded -- forced collapsed to
   56px only on /search, so this is reachable on every OTHER shell page) at a 940px viewport, the
   toolbar's own measured natural width was 863px and `document.documentElement.scrollWidth`
   (1087px) exceeded `clientWidth` (940px) by exactly 224px -- the rail's own width, confirming a
   PURE viewport-width media query cannot account for how much of that viewport the rail is
   currently spending, and this repository has no container-query precedent to reach for instead.
   No single breakpoint is provably safe for every client either: the natural width above is
   driven by THIS client's event/association name lengths ("American Bus Marketplace 2027",
   "American Bus Association"), and a client with longer names needs more room, not less.

   THE FIX below is a second, WIDER escape hatch -- 1200px, chosen as the measured 863px
   requirement plus the rail's own worst-case 224px plus a margin for longer names on other
   clients, not a value with its own live measurement behind it the way 900px has. It is the
   anti-overflow RESET (return the toolbar to normal flow and let it wrap; the inline min-width is
   dropped at every width by the unconditional rule above, so this block no longer repeats it)
   for every width up to 1200px -- the wrap itself applies at every width since item 3514
   (SD-3559 R14). (A 96px COSMETIC narrowing once sat in a 900px block below; SD-3446 R25 removed
   it.) Wrapping onto a second line, as the 1200px rules below allow, is the correct fallback for
   the band in between. Above 1200px the every-width wrap now answers a client with longer names;
   this number still decides where the toolbar leaves `position: absolute` for normal flow -- this
   comment says why the number is what it is so that a revision does not have to re-derive it from
   nothing. */
@media (max-width: 1200px) {
  .shell-topbar {
    height: auto;
    flex-wrap: wrap;
  }
  .shell-topbar .dxbl-toolbar {
    position: static !important;
    width: auto !important;
    height: auto !important;
  }
  /* :not(.dxbl-virtual-toolbar) excludes DxToolbar's own hidden measurement clone (rendered
     off-screen at top:-10000px, class `dxbl-virtual-toolbar dxbl-btn-toolbar`, confirmed by
     reading the live DOM) -- forcing IT to `position: static` too would pull a second, invisible
     copy of every scope chip into normal flow and reintroduce an overflow of its own.
     Item 3596 (AC2, EV1; docs/reviews/3596/browser-layout.md, F2): back in flow, this box is a flex item of
     DxToolbar's `display: flex` root, and at the default `flex: 0 1 auto` it shrank to its content -- 845 of the
     bar's 1144px at 1200px, 484 of 544px at 600px -- so the right-aligned group's `margin-left: auto` stopped short of
     the bar's right end and the phone pickers short of its width. `flex: 1 1 auto` grows it to the bar; `width: auto`
     still drops the script's inline width, as above. No `!important`: DevExpress 26.1.6 sets no `flex` on this box. */
  .shell-topbar .dxbl-btn-toolbar:not(.dxbl-virtual-toolbar) {
    position: static !important;
    width: auto !important;
    height: auto !important;
    flex: 1 1 auto;
  }
  .topbar-scopes {
    flex-wrap: wrap;
  }
  .topbar-scopes,
  .topbar-farright {
    row-gap: 12px;
    padding: 8px 0;
  }
  .topbar-scope-picker {
    flex-wrap: wrap;
  }
}

/* Item 3596 R5, R6, R11 (approved mockup docs/design/ui/shell/2026-10-30-topbar-wrap-mockup.html, frame 3; the preview
   the product owner accepted at 390px, docs/superpowers/briefs/3596/2026-10-29-mobile-preview.md). At 600px and below
   the title box takes a row of its own and the two pickers stack in it, one per row, each the full width of the bar:
   the label keeps its own width and the combo fills the rest. SD-3559 R3's 32vw / 26vw cap is lifted here (its 80px
   floor stays), so a name that still does not fit keeps the ellipsis. `flex-wrap: nowrap` undoes the 1200px block's
   picker wrap, so item 3560's padlock stays inside the event chip on the combo's row and the combo shrinks instead
   (spec C9). The empty `.topbar-scopes` region leaves layout, or its 1200px-block padding would add an empty row.
   Layout properties only, no `!important` (R11): each rule wins over a Team365 rule on specificity or, for the
   unscoped scope rules (item 3560 AC6 forbids an ancestor on them), on source order -- this block comes after the
   1280px and 1200px blocks -- and DevExpress 26.1.6 sets none of these properties on these boxes. */
@media (max-width: 600px) {
  .shell-topbar .dxbl-toolbar-title {
    flex: 1 1 100%;
  }
  .shell-topbar .topbar-scopes:empty {
    display: none;
  }
  .shell-topbar .topbar-farright {
    flex: 1 1 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    margin-left: 0;
    width: 100%;
  }
  .topbar-scope-picker {
    width: 100%;
    box-sizing: border-box;
    flex-wrap: nowrap;
  }
  .topbar-scope-picker > label {
    flex: 0 0 auto;
  }
  .topbar-scope-combo {
    flex: 1 1 auto;
    max-width: none;
  }
}

/* ---------------------------------------------------------------------------------------------
   Footer action bar -- ActionBar.razor's three regions, the legacy's own widths and alignment
   (ButtonFrame/button.html:95-101 -- colLeft 33% left, colCenter 34% centre, colRight 33%
   right; button.html's lines 83-88 are an unrelated comment block, not the table). No new
   tokens: reuses --pane/--line, already owned by tokens.css (see this file's own
   header comment on token ownership) and already themed for both light and dark.

   WHERE THE BAR LIVES. The page renders <ActionBar> itself, inside its own <EditForm>, and THIS
   FILE contributes the footer's geometry: the rules below style an element that sits inside
   .shell-body, and the two rules that follow .actionbar are what pin it to the bottom. A
   SectionOutlet in MainLayout beside .shell-body, with the page projecting its buttons in
   through SectionContent, failed twice on the search screen (2026-09-09): the Search button
   painted OUTSIDE the page's own <form> and so submitted nothing, and the footer was never
   adopted by the circuit because a section's registry is Dispatcher.SectionRegistry -- one per
   renderer. ActionBar.razor's doc comment carries the live-DOM measurements and the reflection
   evidence.

   NO gap between the three regions. The three regions are
   flex: 0 0 33% / 34% / 33%, which is already 100% of the content box before any gap is added;
   a 10px row gap on top of that pushes the content box 19.99px past the viewport at a measured
   1226px width (content 1142px, regions 1141.99px, + 2 * 10px gap), which scrolls the whole
   document sideways and clips .actionbar-right. The legacy has no gap either -- it is a plain
   <table> with no cellspacing/border, TEAM1's own button.html:95-101 -- so no gap is both
   the fix and the faithful port. Breathing room between buttons inside one region comes
   from that region's own inner gap (8px, below), which is not a cause of the overflow.

   PINNED to the bottom of the VIEWPORT, not just the bottom of the document, per
   TEAM1/TeamFiles/TEAM/Admin/index.html:45-53 (docs/rules/RULES.md §3): the legacy's button
   strip lives in its own frameset row, fraBtn, with scrolling="no" and noresize, while fraBody
   (lines 34-43) is the ONLY frame with scrolling="auto" -- so the legacy bar never scrolls
   away, no matter how long the criteria form gets. Measured without the pin: bar top 1181px
   against a 1124px viewport at 1226px wide, and 1705px against 800px at the 900px breakpoint --
   905px below the fold.

   `position: sticky; bottom: 0` keeps both halves of the constraint at once: the document still
   grows (nothing here constrains any ancestor's height, which the product owner requires -- the
   criteria rail must grow the page rather than scroll internally, see search.css's own comment
   on .search-editor, formerly .search-rail; item 3516), and the bar still tracks the bottom of the browser window as that document
   scrolls. Sticky needs two things:

     1. A scrollport that is the viewport. Sticky offsets are computed against the nearest
        ancestor scroll container, and `overflow: auto` creates one even when it never scrolls.
        .shell-body sets no `overflow`: with the bar inside it, `overflow: auto` would make
        .shell-body the scrollport -- see .shell-body's own comment above for why leaving it
        off is safe. Nothing else in the chain
        (.actionbar-host, .shell-body, .shell-main, .shell) sets `overflow`, and
        .search-shell sets no `overflow` either, so neither this footer bar nor the search index
        has a scroll container between it and the window.
     2. A containing block with room to slide in. That is .actionbar-host -- the page's own
        <form> -- which spans the full grown height because it is `flex: 1` of .shell-body,
        itself `flex: 1` of .shell-main.

   This is the same mechanism the search grid's header relies on (search.css, the sticky-header
   comment under .search-shell .search-grid): there the DevExpress theme's sticky <thead> pins
   inside the grid's own scroller, .dxbl-scroll-viewer-content, which scrolls because
   .search-grid is capped with a max-height; here the bar pins against the
   document's own scroll instead.

   `background: var(--pane)` (below, already themed both ways) is doing double duty: the criteria
   rail's own content scrolls behind the bar as the page moves, so an opaque, theme-aware fill is
   load-bearing, not cosmetic -- a transparent bar here would let scrolled fields show through
   it. No new token: still --pane/--line, per this file's own token-ownership comment above.
--------------------------------------------------------------------------------------------- */
.actionbar {
  position: sticky;
  bottom: 0;
  z-index: 1;
  flex: none;
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: 9px 14px;
  background: var(--pane);
  border-top: 1px solid var(--line);
}

/* Put this on the routed page's own root element -- the search screen puts it on its <EditForm>,
   which renders the <form> the Search button must be a descendant of. It does two jobs, both of
   which have a test or a measurement behind them:

   `flex: 1` + column flex makes the page fill the shell body's height, which gives .actionbar's
   sticky positioning a containing block that reaches the bottom of the document.

   `margin-top: auto` on the bar itself then pushes it to the BOTTOM of that column, so a page
   shorter than the viewport still shows the bar at the bottom of the window instead of floating
   directly under the content mid-screen. That is not hypothetical for /search: the page is
   taller than the viewport at 1226x1124 (measured) but need not be on a tall monitor.

   Deliberately NOT `.shell-body > form` -- that would hard-code "a page's root is a form" into
   shell chrome. A page opts in by name; a page with no footer (all five placeholders) does not,
   and gets ordinary top-aligned block-like layout. Checked against
   devexpress.blazor.themes 26.1.4's blazing-berry.bs5.min.css and blazing-dark.bs5.min.css:
   no `.actionbar-host` rule in either, so there is nothing to collide with. */
.actionbar-host {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.actionbar-host > .actionbar {
  margin-top: auto;
}

.actionbar-left,
.actionbar-center,
.actionbar-right {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.actionbar-left {
  flex: 0 0 33%;
  justify-content: flex-start;
}
.actionbar-center {
  flex: 0 0 34%;
  justify-content: center;
}
.actionbar-right {
  flex: 0 0 33%;
  justify-content: flex-end;
}

/* Narrow-width collision (b1a-actionbar-collision.md) -- below 900px the fixed 33/34/33 split
   above has nowhere to shrink to (every region carries `min-width: 0`, so the CONTENT overflows
   its own box rather than the box growing) and ResultsPager.razor's <DxPager> paints on top of
   Reset/Search. Measured on the unpatched bar at 375px, a 415-page result: .actionbar-right was
   given x 265->361 (96px) but the pager drew at x 128->361 (233px) -- 137px left of its own box,
   fully covering both buttons -- and independently, .actionbar-center's own 99px was already
   short of the 142px Reset+8px gap+Search needs, so the buttons were overflowing their OWN region
   into both neighbours before the pager is even considered. Re-measured here with a live 7-page
   "full mode" pager (First/Previous/five page numbers/Next/Last, ABA, term "al", 253 rows) --
   the worst case this screen renders, wider than the 415-page "Go to page" box regime -- and the
   same collision reproduces at every width below 900px.

   900px is not a breakpoint invented here: it is the search screen's one-pane boundary (item 3597 R3;
   `SearchTiers.OnePaneMaxWidth`), and the action bar wraps there too, rather than picking a
   second number.

   THE FIX IS WRAPPING, PAGER ON ITS OWN LINE -- the shape that
   falls out for free once the three regions are allowed onto separate rows: `flex-wrap` lets
   .actionbar-right drop below .actionbar-center instead of overlapping it, and DOM order alone
   (Left, Center, Right -- ActionBar.razor) puts Reset/Search above the pager without an explicit
   `order`. `.actionbar-left` is always rendered too (ActionBar.razor's own doc comment: "all three
   divs render together... rather than only the region actually supplied") but is empty on every
   screen that exists today, including this one -- `:empty` drops it out of flow so its own now-
   full-width, zero-content row doesn't add a blank line above Center. `row-gap: 8px` only,
   deliberately not the shorthand `gap`: this file's own comment on
   `.actionbar`, above, shows that a COLUMN gap on top of three already-100%-wide regions pushes the
   content box past the viewport and scrolls the page sideways -- not a risk here since every
   region is its own full-width row once wrapped (nothing sits beside anything to need a column
   gap), but stating `row-gap` alone rather than `gap: 8px 0` documents that the zero is
   deliberate, not a default nobody looked at twice.

   TOUCH TARGETS -- Apple HIG 44pt / Material 48dp. Measured without this
   rule: DxPager's own buttons are 30x30px square icon buttons (`.dxbl-btn` inside `dxbl-pager`),
   and Reset/Search are 63x30 / 71x30. `min-height: 44px` alone is enough for Reset/Search (already
   wider than 44 in their other dimension) but NOT for the pager's square icon buttons, whose
   `min-width` is separately pinned to 30px by the theme's own
   `.dxbl-pager > .dxbl-btn.dxbl-btn-outline-secondary` rule (blazing-berry.bs5.min.css /
   blazing-dark.bs5.min.css) -- three classes' worth of specificity, which a plain
   `.actionbar .dxbl-btn` (two classes) cannot outrank regardless of load order. Matched instead
   of overridden with `!important`: `.actionbar .dxbl-pager .dxbl-btn.dxbl-btn-outline-secondary`
   is four classes to the theme's three, so it wins on specificity alone, the same way this file's
   own token rules already win light/dark ties by being declared later (see the header comment) --
   consistent with search.css's own precedent for reaching into `dxbl-*` internals
   (`.search-shell .search-grid .dxbl-grid-table`) rather than reaching for `!important`.

   Verified against a live render at 320/375/768/900/1024/1280px, both the numeric-button pager
   (2-10 pages) and the "Go to page" input-box pager (400+ pages, and the 37-page case in between,
   which already switches to the input box -- DxPager's own NavigationMode="Auto" decides that
   threshold, not this stylesheet) -- no overlap and nothing clipped at any width, in both
   directions, in both light and dark. Above 900px every one of these rules is inert: the block
   never fires, so the 1024/1024px desktop measurements (h:30px buttons, 33/34/33 columns, no
   wrap) are byte-for-byte what they are without this block.

   HORIZONTAL DOCUMENT SCROLL AT 320/375px IS NOT THIS BLOCK'S: measured scrollWidth 712px at a
   320px viewport with this block present and with it entirely absent alike -- traced to the top
   bar's own `.topbar-scopes`/`DxToolbar` region overflowing, not to `.actionbar`, and left to
   the narrow-width overflow block above (b1a-actionbar-collision.md's own scope note). The
   action bar's own widest edge stays inside the viewport at every width tested above. */
@media (max-width: 900px) {
  .actionbar {
    flex-wrap: wrap;
    row-gap: 8px;
  }
  .actionbar-left,
  .actionbar-center,
  .actionbar-right {
    flex: 1 1 100%;
  }
  .actionbar-left:empty {
    display: none;
  }
  .actionbar .dxbl-btn {
    min-height: 44px;
    min-width: 44px;
  }
  .actionbar .dxbl-pager .dxbl-btn.dxbl-btn-outline-secondary {
    min-width: 44px;
  }
}

/* ---------------------------------------------------------------------------------------------
   Placeholder pages -- Dashboard/Reports/Communication/Setup/OnSite.

   These rules live here once rather than in an inline <style> block in each of those .razor
   files: shell chrome belongs to shell.css.

   NOT ".placeholder", and the reason is worth keeping next to the rules rather than only in the
   pages: DevExpress's own blazing-berry.bs5.min.css bundles Bootstrap's `.placeholder`
   skeleton-loading utility (opacity:.5; background-color:currentColor; display:inline-block;
   an infinite placeholder-glow animation). A class named `.placeholder` does not override that
   -- it only adds properties Bootstrap's version does not set -- so the two composed into a
   translucent animated box that rendered every placeholder page's text as good as invisible.
   Found by reading getComputedStyle on a rendered /dashboard, not from the markup.
   `.shell-placeholder` and `.shell-placeholder-status` appear in neither blazing-berry.bs5 nor
   blazing-dark.bs5. App.razor loads this file AFTER both the DevExpress theme and search.css,
   so these rules also win any future collision on specificity-tie order. No colour and no new
   token: the text colour is .shell's own `color: var(--ink)`, inherited, which is why nothing
   here needs a dark-mode block.
--------------------------------------------------------------------------------------------- */
.shell-placeholder {
  max-width: 42rem;
  margin: 3rem auto;
  padding: 0 1.5rem;
}
.shell-placeholder h1 {
  margin-bottom: 0.75rem;
}
.shell-placeholder p {
  line-height: 1.5;
}
.shell-placeholder-status {
  margin-top: 1.5rem;
  font-style: italic;
  opacity: 0.75;
}

/* ---------------------------------------------------------------------------------------------
   Nav rail -- collapses to 56px icons by default, pins to 224px (mockup ".rail"/".railnav").
--------------------------------------------------------------------------------------------- */
.nav-rail {
  background: var(--nav);
  color: var(--nav-ink);
  width: 56px;
  flex: none;
  transition: width .16s ease;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.nav-rail--expanded {
  width: 224px;
}

.nav-rail__head {
  height: 48px;
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  border-bottom: 1px solid var(--nav-hover);
}
.nav-rail__mark {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  display: grid;
  place-items: center;
  background: var(--nav-active);
  color: var(--nav-ink-strong);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: -.02em;
}
.nav-rail__wordmark {
  white-space: nowrap;
  font-weight: 700;
  font-size: 13.5px;
  color: var(--nav-ink-strong);
  opacity: 0;
  transition: opacity .12s;
}
.nav-rail--expanded .nav-rail__wordmark {
  opacity: 1;
}

.nav-rail__items {
  padding: 8px 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
}

.nav-rail__item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 0 14px;
  height: 38px;
  color: var(--nav-ink);
  text-decoration: none;
  text-align: left;
  position: relative;
  transition: background .12s, color .12s;
}
.nav-rail__item:hover {
  background: var(--nav-hover);
  color: var(--nav-ink-strong);
}
/* NavLink's default ActiveClass -- applied automatically to whichever item matches the
   current route. Mirrors the mockup's [aria-current="page"] look; NavLink itself also sets
   aria-current="page" on the active link, so the same rule doubles as the accessible state. */
.nav-rail__item.active {
  background: var(--nav-active);
  color: var(--nav-ink-strong);
  font-weight: 600;
}
.nav-rail__item.active::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6px;
  bottom: 6px;
  width: 3px;
  background: var(--nav-ink-strong);
  border-radius: 0 2px 2px 0;
}
.nav-rail__icon svg {
  flex: none;
  width: 17px;
  height: 17px;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.nav-rail__label {
  white-space: nowrap;
  opacity: 0;
  transition: opacity .12s;
  font-size: 12.5px;
}
.nav-rail--expanded .nav-rail__label {
  opacity: 1;
}

.nav-rail__foot {
  flex: none;
  border-top: 1px solid var(--nav-hover);
  padding: 8px 0;
}
.nav-rail__pin {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 0 14px;
  height: 38px;
  color: var(--nav-ink);
  text-align: left;
  background: none;
  border: none;
  font: inherit;
  cursor: pointer;
}
.nav-rail__pin:hover {
  background: var(--nav-hover);
  color: var(--nav-ink-strong);
}

/* ---------------------------------------------------------------------------------------------
   Circuit-failure notice (App.razor's own comment, right above the two elements, explains WHY
   there are two and where each class name came from -- read that first).

   HIDDEN AT REST WITHOUT REFLOW, and that is one mechanism doing both jobs: `position: fixed`
   takes each element out of document flow entirely, so toggling it on never shifts anything else
   on the page (there is nothing for the shell or a routed page to reflow around), and `display:
   none` at rest costs nothing extra beyond that -- no separate "reserve the space" trick is
   needed because a fixed element never had layout space to reserve in the first place. Both
   notices share this base; only the colour differs (below), because one is "this never worked"
   (--stop) and the other is "this did work, and now needs attention" (--warn) -- both already
   declared and themed both ways by tokens.css (this file's own token-ownership comment
   above), so neither notice adds a token.
--------------------------------------------------------------------------------------------- */
.circuit-notice {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 9999;
  width: calc(100% - 32px);
  max-width: 30rem;
  padding: 14px 16px;
  border-radius: 6px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, .25);
  font-size: 13.5px;
  line-height: 1.5;
}
.circuit-notice__title {
  margin: 0 0 4px;
  font-weight: 700;
}
.circuit-notice__body {
  margin: 0 0 10px;
}
.circuit-notice__action {
  border: 1px solid currentColor;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 12.5px;
  padding: 4px 10px;
  border-radius: 4px;
  cursor: pointer;
}

/* Case 1, "never connected" (App.razor's JS: a bounded startup timer, since nothing in the
   framework signals this case). Hidden until App.razor's own script adds
   .circuit-notice--visible -- there is no other way this class is ever added, so at rest
   (no JS has run, or JS ran and the circuit opened in time) it stays display:none. */
#circuit-notice {
  display: none;
  background: var(--stop-bg);
  color: var(--stop);
  border: 1px solid var(--stop);
}
#circuit-notice.circuit-notice--visible {
  display: block;
}

/* Case 2, "connected, then dropped". The framework toggles these seven class names directly on
   this element (App.razor's comment names the exact file and lines this was read from) -- this
   file only says what each one looks like. Starts on components-reconnect-hide, matching the
   steady state the framework itself returns to after a successful reconnect. */
#components-reconnect-modal {
  display: none;
  background: var(--warn-bg);
  color: var(--warn);
  border: 1px solid var(--warn);
}
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-paused,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected,
#components-reconnect-modal.components-reconnect-resume-failed {
  display: block;
}

/* Two paragraphs and a button are already in the markup for each register (App.razor) --
   CSS picks which one is visible rather than JS rewriting text, since the framework already
   hands us the state as a class. Retrying/showing/paused reads as "still working on it";
   failed/rejected/resume-failed (retries exhausted, or the server explicitly refused to
   resume) reads as "your turn, reload" and is the only state offering the Reload button. */
#components-reconnect-modal .circuit-notice__reload-text {
  display: none;
}
#components-reconnect-modal.components-reconnect-failed .circuit-notice__retrying-text,
#components-reconnect-modal.components-reconnect-rejected .circuit-notice__retrying-text,
#components-reconnect-modal.components-reconnect-resume-failed .circuit-notice__retrying-text {
  display: none;
}
#components-reconnect-modal.components-reconnect-failed .circuit-notice__reload-text,
#components-reconnect-modal.components-reconnect-rejected .circuit-notice__reload-text,
#components-reconnect-modal.components-reconnect-resume-failed .circuit-notice__reload-text {
  display: block;
}

/* Item 102 (spec R9; product owner, round 2026-10-30, Q2): on a page that renders the footer action bar, the
   reconnect toast sits ABOVE the bar instead of over it. At the shared 24px offset above, it covered the top 15 of
   the 30 pixel rows of Reset and Search at 1366x768 and 1920x1080 (docs/superpowers/briefs/102/item102-repro.md,
   Verified 5) -- the two buttons a user reaches for when the connection drops. Only the offset moves: look and copy
   are unchanged (R9), and #circuit-notice (never connected) keeps its 24px.

   73px is the bar's 49px on one line plus the toast's own 24px; 95px at 900px and below is the wrapped bar's 71px
   (measured at 820px) plus the same 24px. Both heights are search.css's measurements, beside its
   html:has(.search-shell) scroll-padding-bottom (docs/reviews/3515/browser-check.md); the company pages' bar is not
   measured here, and item 102's browser pass records it. MainLayout renders .shell and the two notices as siblings,
   and every page that owns actions renders <ActionBar> inside .shell-body, so this matches exactly when the bar is
   on the page. :has() is already this stylesheet's accepted browser floor (SD-57). */
.shell:has(.actionbar) ~ #components-reconnect-modal {
  bottom: 73px;
}
@media (max-width: 900px) {
  .shell:has(.actionbar) ~ #components-reconnect-modal {
    bottom: 95px;
  }
}

/* ---------------------------------------------------------------------------------------------
   Touch ergonomics (2026-09-11 tablet tier, docs/superpowers/plans/2026-09-11-tablet-tier.md).

   Keyed on the POINTER, not on a width, and that is the whole point of the block: tablet
   portrait at 820 CSS px is the supported floor (iPad Air and up, SD-56), but the 13" iPad Pro in
   portrait is 1024 CSS px -- above every breakpoint this block and the shell block below it
   use -- and is still driven by a finger, while an 820px browser window on a desktop sits
   exactly at that floor and is driven by a mouse. `pointer: coarse` asks the question that
   actually matters. The trade against `any-pointer: coarse` (which would also catch a tablet in
   a trackpad keyboard case, and would also enlarge targets on a touchscreen laptop being driven
   with a mouse) is recorded as R8 in the plan.

   44px is the smaller-dimension floor the tier requires. Nothing here changes a font size:
   type size is density, and density is settled by docs/spikes/2026-09-05-form-density.md.

   MEASURED BEFORE AND AFTER, in docs/spikes/2026-09-11-tablet-measurements.md: at 820px with a
   mouse these three are 38, 38 and 32px, and they stay 38, 38 and 32px -- this block does not
   fire for a fine pointer.

   NOT included, deliberately: the DevExpress toolbar items' and editors' own hit boxes. Those are
   dxbl-* chrome that App.razor's own comment says this codebase does not override, and the
   vendor's lever for them is the SizeMode parameter (C#, per component), not CSS. The spike
   measured them at 28-30px, under the 44px floor: that is a real gap with no cheap fix (plan R6),
   not something to paper over from here. The one exception already in this file is the action
   bar's own `min-height: 44px` inside the `max-width: 900px` block above -- a width-keyed answer
   to the same question. Different selectors,
   different rule, no collision.
--------------------------------------------------------------------------------------------- */
@media (pointer: coarse) {
  /* Both are height: 38px for a fine pointer. The rail is 56px wide when collapsed, so 38 is the
     smaller dimension and therefore the one the floor applies to. min-height rather than height
     so a wrapped label in the expanded rail grows the item instead of overflowing it. */
  .nav-rail__item,
  .nav-rail__pin {
    height: auto;
    min-height: 44px;
  }

  /* height: 32px for a fine pointer. The chip is a flex row around a DxComboBox; growing the chip
     does not grow the combo's own input, which is DevExpress's (see the block comment above). */
  .topbar-scope-picker {
    height: auto;
    min-height: 44px;
  }
}

/* ---------------------------------------------------------------------------------------------
   Tablet tier -- the nav rail below 900px (2026-09-11,
   docs/superpowers/plans/2026-09-11-tablet-tier.md).

   900px is this codebase's incumbent narrow-width boundary -- search.css carries
   `@media (max-width: 900px)`, and the action bar's block above already uses it (the top bar's own
   900px block was removed by item 3514) -- so the tier keeps it for shell chrome rather than
   inventing a second number. It covers the 820px supported floor (iPad Air portrait, SD-56) with
   room above it. The search screen's own tiers are 1280px and 900px (item 3597) and no longer stack:
   see search.css.

   THE EXPANDED RAIL OVERLAYS INSTEAD OF TAKING FLOW WIDTH. .nav-rail--expanded is 224px, which
   is 27% of an 820px viewport spent on six links. Forcing it collapsed here would be one line,
   and would leave NavRail's pin button visibly doing nothing -- an inert control, the defect
   ResultsPager.razor's own doc comment also refuses. So the panel leaves flow and paints over the
   page instead, and the pin keeps working in both directions.

   `position: absolute` against `.shell` (which is min-height: 100vh and grows with the
   document) rather than `position: fixed`, deliberately: fixed would pin the rail to the
   VIEWPORT, so a long page scrolled halfway down would show the rail's foot floating in the
   middle of the screen. Absolute against .shell makes it as tall as the document, which is what
   the 56px collapsed rail already is.

   `.shell:has(.nav-rail--expanded)::before` is the flow-width spacer, and it is load-bearing:
   an absolutely positioned child leaves flow, so without it .shell-main would widen by 56px the
   instant the pin is pressed and every page would jump sideways under the overlay. :has() is
   what lets the spacer exist only while the rail is expanded -- the alternative is a class on
   .shell, which means state and markup in MainLayout.razor, which takes this out of CSS.

   :has() STAYS, AND THIS IS SETTLED (plan R10; product owner, 2026-09-11 -- Option A). Do not
   replace it with a class on .shell. MainLayout.razor renders <div class="shell"> statically,
   <NavRail @rendermode="InteractiveServer" /> is an interactive island inside it, and
   IsExpanded (_pinned && !IsOnSearchScreen) lives inside NavRail -- so stamping a class from the
   layout needs either a scoped service plus interactivity on the shell, or an interactive
   MainLayout, which this codebase deliberately avoids. The price of keeping :has() is a
   supported-browser floor, recorded as SD-57; :has() is what forces it.

   z-index: 30 is chosen against the two other stacking values in this stylesheet, not picked at
   random: .actionbar is z-index: 1 and the circuit-failure notices are z-index: 9999 (and the
   search grid's sticky header is 2, set by the DevExpress theme rather than search.css). The
   rail must cover the sticky action bar -- it is chrome the viewer just asked to see -- and
   must not cover a notice saying the circuit died.

   ONLY ONE OF THOSE TWO IS VERIFIED, and the other cannot be verified today. The notice case was
   checked live at 820px on /dashboard: with the rail expanded and #components-reconnect-modal
   forced visible, elementFromPoint at a coordinate inside both boxes returns the notice. The
   ACTION BAR case was not, because there is no page to check it on -- /dashboard and the other
   placeholder pages render no <ActionBar>, and the one screen that does, /search, forces the rail
   collapsed (NavRail.IsExpanded is _pinned && !IsOnSearchScreen). So 30 > 1 is an argument from
   the declared values, not a measurement, and it becomes checkable the first time a second screen
   renders an action bar.

   .nav-rail--expanded's own `width: 224px` is NOT repeated here: the base rule still supplies it,
   and repeating it would create a second place to change it.
--------------------------------------------------------------------------------------------- */
@media (max-width: 900px) {
  .shell {
    position: relative;
  }

  .shell:has(.nav-rail--expanded)::before {
    content: "";
    flex: none;
    width: 56px;
  }

  .nav-rail--expanded {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 30;
    box-shadow: 2px 0 12px rgba(0, 0, 0, .28);
  }
}

/* ---------------------------------------------------------------------------------------------
   Sign-in -- Login.razor and SignOut.razor, both on PublicLayout (SD-64).

   Declares NO tokens. tokens.css owns the base palette and App.razor links it on
   every page, so --bg/--pane/--ink/--line/--accent are already in scope here and both the
   light and dark themes come free. This file's own header comment states that ownership rule;
   redeclaring a colour here would be the second place a value lives.

   Class names are deliberately NOT `.placeholder`, `.card` or anything else generic: DevExpress
   ships Bootstrap in blazing-berry.bs5.min.css, and its `.placeholder` skeleton-loading utility
   would compose with a page's own class of the same name into a translucent animated box that
   would render the text as good as invisible (Dashboard.razor's closing comment records it).
--------------------------------------------------------------------------------------------- */
.login {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--bg);
}

.login-card {
  width: 100%;
  max-width: 380px;
  padding: 32px;
  background: var(--pane);
  border: 1px solid var(--line);
  border-radius: 8px;
}

.login-title {
  margin: 0 0 24px;
  font-size: 20px;
  font-weight: 600;
  color: var(--ink);
}

.login-field {
  margin-bottom: 16px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.login-field label {
  font-size: 13px;
  color: var(--ink2);
}

.login-input {
  width: 100%;
  padding: 8px 10px;
  font: inherit;
  color: var(--ink);
  background: var(--pane);
  border: 1px solid var(--line);
  border-radius: 4px;
}

.login-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.login-error {
  margin: 0 0 16px;
  font-size: 13px;
  color: var(--stop);
}

.login-submit {
  width: 100%;
  padding: 9px 12px;
  font: inherit;
  font-weight: 600;
  color: #FFFFFF;
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 4px;
  cursor: pointer;
}

.login-submit:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------------------------------
   Record History -- RecordHistory.razor (item 3451, SD-3500).

   Declares NO tokens and no literal colours: tokens.css owns the palette, so both themes come
   free. --ink2 is the results bar's own count colour (search.css .resbar .count), --stop the
   sign-in error's. The list itself is a DxGrid and carries no rule here.
--------------------------------------------------------------------------------------------- */
.record-history__count {
  margin: 0 0 8px;
  font-size: 12px;
  color: var(--ink2);
}

.record-history__changes {
  list-style: none;
  margin: 0;
  padding: 0;
}

.record-history__empty {
  margin: 0;
  font-size: 13px;
  color: var(--ink2);
}

.record-history__failed {
  margin: 0;
  font-size: 13px;
  color: var(--stop);
}
