/*
 * BrickDb component styles (design-system.md §5)
 * ----------------------------------------------------------------------------
 * Shared component rules whose BEM blocks are all pinned to real renderers by
 * ComponentBlockIsRenderedTests. Retired blocks stay deleted until their component exists.
 *
 * Rules that apply throughout, per the brief:
 *   - Authored at 360px first; every widening step is a min-width media query
 *     (600 / 905 / 1240 / 1440, matching design-system.md §4).
 *   - Every interactive target is >=44x44px.
 *   - Nothing requires :hover to reveal an action — hover-only refinements are
 *     wrapped in @media (hover: hover) and (pointer: fine) so touch/keyboard
 *     users never lose an affordance.
 *   - No page scrolls horizontally at any width; wide content scrolls inside
 *     its own container.
 *   - Class names are prefixed bd-, BEM-ish (block__element--modifier).
 * ----------------------------------------------------------------------------
 */

/* =========================================================================
   GlassSurface (§2.4.2) — ported from OutaStory's own GlassSurface.razor /
   ui-v2-worlds.css, retinted to BrickDb's own palette. Chrome only: TopBar,
   AppSidebar, sheets, modals, overlay surfaces. Never a card, list
   row, or anything inside a scrolling region — see Components/GlassSurface.razor and
   design-system.md §2.4.3.

   background/border/box-shadow are set UNCONDITIONALLY; only backdrop-filter (the blur
   itself) is wrapped in @supports, with the -webkit- prefix alongside. That means the
   un-blurred state is never a different, separately-verified "fallback" colour — it's
   the same translucent fill, just without blur, which design-system.md §2.4.3's proof
   already covers because that proof never depended on blur being present. This is what
   keeps GlassSurface correct on older Android WebView (pre-Chromium "blink"
   backdrop-filter support, still present on some Play/Amazon/Samsung-shipped devices —
   see the store matrix) with zero extra work: the @supports block just never fires there.
   ========================================================================= */

.bd-glass {
  background: var(--bd-color-glass-bg);
  border: 1px solid var(--bd-color-glass-border);
  box-shadow: var(--bd-shadow-2), var(--bd-color-glass-highlight);
}

.bd-glass--strong {
  background: var(--bd-color-glass-bg-strong);
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .bd-glass,
  .bd-glass--strong {
    backdrop-filter: blur(var(--bd-glass-blur));
    -webkit-backdrop-filter: blur(var(--bd-glass-blur));
  }
}

/* Three independent user preferences collapse every glass surface to the same opaque,
   already-verified fill (--bd-color-surface-raised) — design-system.md §2.4.2/§8:
   prefers-reduced-transparency (a user who finds translucency hard to read), prefers-
   contrast: more (the concrete mechanism OutaStory's own token comment notes high-
   contrast mode opts out of), and reduced motion is handled separately below since
   transparency and motion are different preferences. */
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  .bd-glass,
  .bd-glass--strong {
    background: var(--bd-color-surface-raised);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

/* prefers-reduced-motion follows the existing rule elsewhere in this file: a sheet's or
   modal's appear/disappear blur/opacity TRANSITION is skipped or capped at
   --bd-duration-instant; the resting glass state itself (this block) is unaffected —
   motion and transparency are separate preferences, gated by separate media queries. */
@media (prefers-reduced-motion: reduce) {
  .bd-glass,
  .bd-glass--strong {
    transition: none;
  }
}

/* =========================================================================
   Shared primitives — buttons
   Used by EmptyState, ConfirmSheet, TopBar and SearchField actions.
   ========================================================================= */

.bd-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--bd-space-2);
  min-height: 44px;
  min-width: 44px;
  padding: 0 var(--bd-space-5);
  border-radius: var(--bd-radius-pill);
  border: none;
  font-family: var(--bd-font-body);
  font-weight: 600;
  font-size: var(--bd-type-body-sm-size);
  line-height: 1;
  white-space: nowrap;
  transition: background-color var(--bd-duration-short) var(--bd-ease-standard),
              color var(--bd-duration-short) var(--bd-ease-standard),
              border-color var(--bd-duration-short) var(--bd-ease-standard);
}

.bd-btn:disabled {
  opacity: 0.5;
  pointer-events: none;
}

.bd-btn--primary {
  background: var(--bd-color-primary);
  color: var(--bd-color-on-primary); /* white since 2026-09-04 — see tokens.css §2.3 */
}

.bd-btn--primary:active {
  background: var(--bd-color-primary-dark);
}

@media (hover: hover) and (pointer: fine) {
  .bd-btn--primary:hover {
    background: var(--bd-color-primary-mid);
  }
}

.bd-btn--secondary {
  background: transparent;
  color: var(--bd-color-on-surface);
  border: 1px solid var(--bd-color-border-strong);
}

.bd-btn--secondary:active {
  background: var(--bd-color-surface);
}

.bd-btn--danger-text {
  background: transparent;
  color: var(--bd-color-danger);
  padding-inline: var(--bd-space-3);
  text-decoration: underline;
  text-decoration-thickness: max(1px, 0.08em);
}

.bd-btn--icon {
  min-width: 44px;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: var(--bd-radius-pill);
  background: transparent;
  color: var(--bd-color-on-surface);
}

.bd-btn--icon:active {
  background: var(--bd-color-surface);
}

.bd-btn__icon-svg {
  width: 22px;
  height: 22px;
  flex: none;
}

/* =========================================================================
   AppShell — root layout: AppSidebar (mobile burger/rail/expanded) +
   content outlet + toast/scrim layers. Owns safe-area insets
   (tokens.css --bd-safe-area-*); the web head's App.razor owns data-theme on <html>.
   ========================================================================= */

.bd-app-shell {
  display: flex;
  min-height: 100dvh;
  background: var(--bd-color-surface);
  color: var(--bd-color-on-surface);
}

.bd-app-shell__main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  /* Base/sm: reserve top clearance for the 56px burger strip, safe-area aware. No side
     clearance — the drawer is off-canvas and overlays, it never pushes content
     (sidebar-navigation-and-home-launcher.md §5). */
  padding-top: calc(56px + var(--bd-safe-area-top));

  /* ...and bottom clearance for the fixed bottom bar (navigation.md §2.4). ON THE SHELL,
     NEVER ON THE FOOTER, which is what the source template does and is its bug in three
     ways at once: a page with no footer or a scrolled-past one is overlapped with nothing
     reserving anything; the template's literal 71px reserves nothing for the home
     indicator; and a margin on one component is invisible to anything that later anchors
     to the viewport bottom. Our footer is INSIDE .bd-app-shell__content, so copying it
     would have reserved space inside the very box the bar overlaps. */
  padding-bottom: calc(64px + var(--bd-safe-area-bottom));
}

.bd-app-shell__content {
  flex: 1 1 auto;
  padding: var(--bd-space-4);
  /* Safe-area-top is carried by .bd-app-shell__main's own padding-top below md (folded
     into the 56px burger-strip clearance above). At md+ it is carried by the decorative
     brick band (BrickRow.razor.css), which since 2026-09-21 is the topmost element in
     the page column and so owns the top edge this element used to. */
  padding-top: 0;
}

@media (min-width: 600px) {
  .bd-app-shell__content {
    padding: var(--bd-space-6);
    padding-top: 0;
  }
}

@media (min-width: 905px) {
  .bd-app-shell__main {
    /* md: the icon rail replaces the burger strip — 72px on the left, nothing on top. */
    padding-top: 0;
    padding-left: 72px;

    /* AND NOTHING AT THE BOTTOM. Not optional: the base rule above is unconditional and this
       block comes later in the same file, so an override that omitted this line would leave
       64px of dead space under every desktop page — and StylesheetCascadeOrderTests guards
       only `display` and `position`, so nothing would be red. That is the twelve-dead-rail-
       overrides shape (sidebar-navigation-and-home-launcher.md §6.2b) pointed at the other
       axis. BottomBarClearanceTests reads both declarations out of this file. */
    padding-bottom: 0;
  }
}

@media (min-width: 1240px) {
  .bd-app-shell__main {
    /* lg/xl: the expanded sidebar reserves ~264px. AppSidebar's collapsed/expanded state
       lives on its own root element (.bd-mobile-topbar), .bd-app-shell__main's direct
       flex sibling, so a plain sibling combinator tracks it — see the rail override
       immediately below — with no need for :has(). */
    padding-left: 264px;
  }

  .bd-mobile-topbar--rail ~ .bd-app-shell__main {
    padding-left: 72px;
  }
}

@media (min-width: 1440px) {
  .bd-app-shell__content {
    max-width: 1280px;
    margin-inline: auto;
    width: 100%;
  }
}

/* =========================================================================
   The fixed bottom bar - phones only (issue #845). Four destinations on a 64px strip,
   gone at 905px where the icon rail takes over. Why those four, and the whole argument:
   docs/ui-refresh/03-information-architecture/navigation.md 2 and
   docs/design/sidebar-navigation-and-home-launcher.md 4.5.

   Three things are load-bearing and cheap to regress:

   SOLID, NEVER GLASS. GlassBudgetTests pins exactly two permanently-blurred surfaces and
   names both by file and class; a third means editing that constant, on the platform least
   able to afford a backdrop-filter.

   --bd-z-bottom-bar (15), BELOW --bd-z-sidebar - which is not the obvious choice and is the
   whole of "an open drawer covers the bar". The drawer's scrim is z-index 0 INSIDE the
   <summary>'s stacking context, and that context is at --bd-z-sidebar, so a bar at 20 ties
   with it and wins on DOM order. Measured: at 20, a tab tap navigated to Auth0 from under
   the scrim. MobileDrawerClosingTests holds it.

   minmax(0, 1fr) TRACKS. The bare-fraction form NoHorizontalScrollSourceGuardTests bans
   cannot even be quoted here - it sweeps the file as text - and a bare fraction's minimum is
   the track's content width, so a long German label would widen the strip.
   ========================================================================= */

.bd-bottom-bar {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: var(--bd-z-bottom-bar);
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  /* 64 and not the strip's 56: 56 leaves no room for icon-over-label. The safe-area term
     appears twice on purpose - once so the bar is tall enough to hold its own padding, once
     as the padding itself. */
  height: calc(64px + var(--bd-safe-area-bottom));
  padding-bottom: var(--bd-safe-area-bottom);
  background: var(--bd-color-surface-raised);
  border-top: 1px solid var(--bd-color-border);
  box-shadow: var(--bd-shadow-3);
}

.bd-bottom-bar__tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--bd-space-1);
  min-width: 0;
  padding-inline: var(--bd-space-1);
  color: var(--bd-color-muted);
  text-decoration: none;
}

/* THE ACTIVE STATE IS THE SIDEBAR'S, VERBATIM: the primary fill goes behind the ICON and
   the label to --bd-color-on-surface. A bare --bd-color-primary on the label measures
   3.68:1 on a dark surface and fails AA. Copying it means no new token and no new
   TokenContrastTests row - all three pairs are already in its table. */
.bd-bottom-bar__tab--active {
  color: var(--bd-color-on-surface);
}

.bd-bottom-bar__icon-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 26px;
  border-radius: var(--bd-radius-pill);
  flex: none;
}

.bd-bottom-bar__tab--active .bd-bottom-bar__icon-wrap {
  background: var(--bd-color-primary);
  color: var(--bd-color-on-primary);
}

.bd-bottom-bar__icon {
  width: 22px;
  height: 22px;
  flex: none;
}

/* Caption, not --bd-type-label: that token is caps with tracking, for chips and badges, and
   costs width German cannot spare. Truncated rather than wrapped, so the bar's height is
   never a function of a translation. */
.bd-bottom-bar__label {
  max-width: 100%;
  font-size: var(--bd-type-caption-size);
  line-height: var(--bd-type-caption-line);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Gone at md and above. PaintedNavigationTests asserts both directions: a guard that only
   checked it paints at 390px would pass on a bar that never hides. */
@media (min-width: 905px) {
  .bd-bottom-bar {
    display: none;
  }
}

/* =========================================================================
   AppSidebar — the one navigation, in three responsive presentations of the same
   ordered list (docs/design/sidebar-navigation-and-home-launcher.md; replaced
   BottomNav/PrimaryNav on 2026-09-09 when Thimo repealed the five-destination ceiling —
   AGENTS.md, design-system.md §4.1/§5).

   ONE <details> ELEMENT DRIVES ALL THREE STATES, never two renderings of the nav list.
   Below 905px it behaves as a native disclosure: <summary> is the fixed 56px top strip
   (burger icon + wordmark), and .bd-sidebar is <details>'s hidden-by-default content,
   revealed as an off-canvas drawer with a scrim when [open]. At 905px and above, an
   author rule overrides the UA default that hides a closed <details>'s non-summary
   children and repositions .bd-sidebar as a persistent fixed column instead — 72px icon
   rail at md, ~264px expanded (or 72px collapsed) at lg/xl.

   Chrome (design-system.md §2.4.3): the burger strip and the sidebar panel are both
   glass, background/border/box-shadow set unconditionally with backdrop-filter itself
   wrapped in @supports, mirroring the GlassSurface pattern above without using the
   component directly — GlassSurface's own root is a plain div, and this markup needs
   <summary>/<nav> for their native semantics.
   ========================================================================= */

/* AppSidebar's whole rendered output roots on this element and the <details> beneath
   it; neither has any box of its own (display: contents), so .bd-app-shell's flex
   layout sees straight through to the real chrome — the fixed burger strip/drawer below
   md, the fixed rail/expanded column at and above it. */
.bd-mobile-topbar,
.bd-sidebar-drawer {
  display: contents;
}

/* ---- the burger strip (<summary>), base/sm only ---- */

/* THE STRIP IS THE CLOSE CONTROL, SO IT MUST NOT BE UNDER THE PANEL (issue #443). It used to
   be: <summary> and .bd-sidebar both carried z-index: var(--bd-z-sidebar), equal, and the panel
   comes later in the DOM - so an open drawer painted straight over the strip, and
   document.elementFromPoint at the summary's own centre returned .bd-sidebar__header. Both
   glyphs were there, the CSS swapped them correctly, every computed style read fine, and the
   one control that closes the drawer could not be tapped. The panel now starts BELOW the strip
   (see .bd-sidebar's own top, further down) rather than the two merely being reordered, because
   disjoint boxes cannot be got wrong again by a later z-index edit.

   IT CARRIES NO background AND NO backdrop-filter. A filtered element is the containing block
   for its own position: fixed descendants, which would pin .bd-sidebar-drawer__scrim to this
   56px box instead of the viewport - and the scrim being a descendant is what makes
   tap-outside-to-dismiss work with no script at all (AppSidebar.razor says why). The glass
   lives on .bd-sidebar-drawer__bar instead. */
.bd-sidebar-drawer__summary {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--bd-z-sidebar);
  height: calc(56px + var(--bd-safe-area-top));
  list-style: none; /* Firefox/Safari draw a disclosure triangle on <summary> by default */
  cursor: pointer;
}

.bd-sidebar-drawer__summary::-webkit-details-marker,
.bd-sidebar-drawer__summary::marker {
  display: none;
  content: "";
}

/* The visible strip. `z-index: 1` inside the summary's own stacking context, so the scrim
   beside it (z-index: 0) dims the page and never this. */
.bd-sidebar-drawer__bar {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--bd-space-3);
  height: 100%;
  padding-top: var(--bd-safe-area-top);
  padding-inline: var(--bd-space-4);
  background: var(--bd-color-glass-bg);
  border-bottom: 1px solid var(--bd-color-glass-border);
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .bd-sidebar-drawer__bar {
    backdrop-filter: blur(var(--bd-glass-blur));
    -webkit-backdrop-filter: blur(var(--bd-glass-blur));
  }
}

/* Keeps the mark and the wordmark as direct flex items of the bar, so the aria-hidden wrapper
   that stops them joining the control's accessible name costs no layout. */
.bd-sidebar-drawer__brand-lockup {
  display: contents;
}

.bd-sidebar-drawer__icon {
  width: 24px;
  height: 24px;
  flex: none;
}

.bd-sidebar-drawer__icon--open {
  display: none;
}

/* The ☰ ↔ ✕ swap: a CSS-only read of <details>'s own [open] state, no script involved. */
details[open] > .bd-sidebar-drawer__summary .bd-sidebar-drawer__icon--closed {
  display: none;
}

details[open] > .bd-sidebar-drawer__summary .bd-sidebar-drawer__icon--open {
  display: block;
}

/* The same swap applied to the control's accessible name - "Open menu" while closed, "Close
   menu" while open. `display: none` is what takes the inactive one out of the accessibility
   tree, so exactly one ever contributes to the name. */
.bd-sidebar-drawer__sr-label--open {
  display: none;
}

details[open] > .bd-sidebar-drawer__summary .bd-sidebar-drawer__sr-label--closed {
  display: none;
}

details[open] > .bd-sidebar-drawer__summary .bd-sidebar-drawer__sr-label--open {
  display: block;
}

.bd-sidebar-drawer__brand-icon {
  width: 26px;
  height: 26px;
  flex: none;
}

/* One weight, one colour, one tracking, wherever the wordmark appears: the mark beside it
   already carries three colours (brand/README.md §1). */
.bd-sidebar-drawer__brand {
  font-family: var(--bd-font-display);
  font-weight: 700;
  font-size: var(--bd-type-body-lg-size);
  letter-spacing: -0.02em;
  color: var(--bd-color-on-surface);
}

.bd-sidebar-drawer__scrim {
  display: none;
}

/* Inside the summary's stacking context, below .bd-sidebar-drawer__bar (z-index: 1) and
   therefore below the panel too - so it dims everything and intercepts nothing a visitor needs
   to reach. `inset: 0` deliberately covers the area behind the panel as well: the panel is
   glass, and a scrim stopping at its edge would show an undimmed page through it. */
details[open] > .bd-sidebar-drawer__summary > .bd-sidebar-drawer__scrim {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 0;
  background: var(--bd-color-scrim);
}

/* ---- the panel itself: <details>'s hidden-by-default content below md, the
   off-canvas drawer when [open], the persistent rail/expanded column at md+ ---- */

/* Chrome, rendered through <GlassSurface> (AppSidebar.razor) — background/border/box-shadow
   come from .bd-glass/.bd-glass--strong unconditionally, this block owns layout only, per the
   rule TopBar's own CSS states. border-right is the one single-edge exception every glass
   chrome block in this file takes (TopBar's border-bottom is the precedent). */
.bd-sidebar {
  position: fixed;
  /* Below the burger strip, never over it - see .bd-sidebar-drawer__summary above for the
     failure that reordering alone would leave available. The safe-area inset is spent by the
     strip's own height here, which is why padding-top is 0 below md and restored at md+, where
     the strip is gone and this column owns the top edge again. */
  top: calc(56px + var(--bd-safe-area-top));
  bottom: 0;
  left: 0;
  z-index: var(--bd-z-sidebar);
  display: none;
  flex-direction: column;
  width: min(85vw, 320px);
  padding-top: 0;
  padding-bottom: var(--bd-safe-area-bottom);
  padding-left: var(--bd-safe-area-left);
  border-right: 1px solid var(--bd-color-border);
  transform: translateX(-100%);
  transition: transform var(--bd-duration-medium) var(--bd-ease-standard);
}

details[open] > .bd-sidebar {
  display: flex;
  transform: translateX(0);
}

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

/* ---- the sidebar's own internal layout, all states ----

   THIS BLOCK SITS ABOVE THE BREAKPOINTS ON PURPOSE, AND IT DID NOT USED TO. A media query
   adds no specificity, so between two equally-specific rules the LATER one wins whatever
   width it was written for. This block was originally below the md/lg blocks, which made
   twelve of their `display` declarations dead — among them the three that hide the group
   headings and the four legal links in the 72px rail and show the compact /legal icon
   instead. The rail therefore painted the expanded presentation's chrome inside a 72px
   column: headings clipped mid-word, legal links overflowing, and a horizontal scrollbar
   inside the navigation that no page-level overflow guard could see, because .bd-sidebar is
   position: fixed and scrolls inside itself. Nothing looked wrong in the stylesheet — the
   rules are all there, in the right breakpoints, struck through in a dev-tools list nobody
   scrolls. Guards: StylesheetCascadeOrderTests (source, in CI) and SidebarPresentationTests
   (a real browser, not in CI). Anything added below a breakpoint block here has to be an
   override of it, never a base. */
/* display: none below md, restored at md+ in the rail block. The drawer now opens beneath the
   burger strip, which already carries the mark and the wordmark, so the panel repeating both
   immediately underneath is the same lockup twice - and the collapse toggle this row also holds
   is an lg/xl affordance that was never shown at this width anyway. */
.bd-sidebar__header {
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: var(--bd-space-2);
  min-height: 56px;
  padding-inline: var(--bd-space-3);
  flex: none;
}

.bd-sidebar__brand-link {
  display: flex;
  align-items: center;
  gap: var(--bd-space-2);
  min-width: 0;
  color: var(--bd-color-on-surface);
  text-decoration: none;
  font-family: var(--bd-font-display);
  font-weight: 700;
  letter-spacing: -0.02em;
}

.bd-sidebar__brand-icon {
  width: 26px;
  height: 26px;
  flex: none;
}

.bd-sidebar__brand-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bd-sidebar__collapse-toggle {
  display: none; /* only ever shown at lg/xl — the md block never restates this */
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: var(--bd-radius-pill);
  background: transparent;
  color: var(--bd-color-muted);
  border: none;
}

.bd-sidebar__collapse-icon {
  width: 18px;
  height: 18px;
  flex: none;
}

/* `flex: 0 1 auto`, not `1 1 auto`: the nav list takes the height it needs and no more, so
   the utility zone below it follows the navigation it belongs to instead of being pushed to
   the bottom of the column. On a 1919px-tall screen the old `1 1 auto` opened a 620px void
   between the last destination and the legal group, which read as detached debris rather
   than as chrome. It still shrinks (`1` in the middle) with `min-height: 0` and its own
   scrollbar when the viewport is too short for the list, which is the case bottom-pinning
   was never about. Guard: SidebarPresentationTests.OnAVeryTallViewport_... */
.bd-sidebar__scroll {
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--bd-space-2) var(--bd-space-2) var(--bd-space-3);
}

.bd-sidebar__group {
  margin-top: var(--bd-space-4);
}

/* 28px rather than 36px (#746): four of these sit in a navigation 311px too tall. */
.bd-sidebar__group-label {
  display: block;
  padding: var(--bd-space-1) var(--bd-space-3) 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--bd-color-muted);
}

/* A flex row (icon-wrap + label), and the flex item of a flex COLUMN one level up
   (.bd-sidebar__scroll/.bd-sidebar__group) rather than of another flex row — but
   min-width: 0 lives here anyway, on the row that actually contains the localized text,
   rather than only on .bd-sidebar__label below: NoHorizontalScrollSourceGuardTests reads
   whichever occurrence of a selector it finds first, and this one has no @media-scoped
   duplicate to be confused with. */
.bd-sidebar__row {
  display: flex;
  align-items: center;
  gap: var(--bd-space-3);
  min-width: 0;
  min-height: 44px;
  padding-inline: var(--bd-space-3);
  border-radius: var(--bd-radius-md);
  color: var(--bd-color-muted);
  text-decoration: none;
  /* position: relative IS LOAD-BEARING (#746, doc 6.2c): without it the rail's absolutely
     positioned labels escape .bd-sidebar__scroll's overflow into the fixed column, whose
     scrollHeight then passes its clientHeight. */
  position: relative;
}

.bd-sidebar__icon-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex: none;
  border-radius: var(--bd-radius-pill);
  transition: background-color var(--bd-duration-short) var(--bd-ease-standard);
}

.bd-sidebar__icon {
  width: 22px;
  height: 22px;
  flex: none;
}

/* The text-bearing flex item inside .bd-sidebar__row (display: flex, row by default):
   min-width: 0 is what lets it truncate instead of forcing the whole 72-320px-wide
   sidebar wider than its own reserved column (NoHorizontalScrollSourceGuardTests). */
.bd-sidebar__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Active row: --bd-color-primary fill + --bd-color-on-primary icon; inactive:
   --bd-color-muted — the same rule BottomNav used, ported unchanged. */
.bd-sidebar__row--active {
  color: var(--bd-color-on-surface);
}

.bd-sidebar__row--active .bd-sidebar__icon-wrap {
  background: var(--bd-color-primary);
  color: var(--bd-color-on-primary);
}

/* ---- the column owns its own link colour (#793's third defect, doc 6.2d) ----

   REPORTED AS "SETTINGS IS THE WRONG COLOUR"; MEASURED, SETTINGS WAS THE ONLY ELEMENT SHOWING
   THE RIGHT ONE. app.css's dark link rule is `:root[data-theme="dark"] a`, (0,2,1), outranking
   the single class every colour declaration here uses - so in dark every anchor here painted
   --bd-color-primary-light while the settings <summary> kept --bd-color-muted. In light it is a
   bare `a { }`, (0,0,1), which a class beats: hence it shipped. `:is()` takes its most specific
   argument, so these are (0,2,1) too - a tie broken by source order. The hover half is here
   because `a:hover`, (0,1,1), used to win. */
.bd-sidebar a:is(.bd-sidebar__row, .bd-sidebar__legal-link, .bd-sidebar__legal-compact) {
  color: var(--bd-color-muted);
}

.bd-sidebar a:is(.bd-sidebar__row--active, .bd-sidebar__brand-link),
.bd-sidebar a:is(.bd-sidebar__row, .bd-sidebar__legal-link, .bd-sidebar__legal-compact):hover {
  color: var(--bd-color-on-surface);
}

/* THIS NEVER SHRINKS, SO ITS HEIGHT IS THE NAVIGATION'S BUDGET (#746, doc 6.2c). space-2 not
   space-3: 8px back, and these rows share .bd-sidebar__scroll's left edge (#793). */
.bd-sidebar__utility {
  flex: none;
  padding: var(--bd-space-2);
  border-top: 1px solid var(--bd-color-border);
  display: flex;
  flex-direction: column;
  gap: var(--bd-space-1);
}

/* ---- the settings block: theme + language, at the foot of the utility zone (issue #419) ----

   ONE ROW THAT OPENS A FLYOUT, AT EVERY WIDTH. The summary is laid out exactly like a
   destination row (it is deliberately NOT given .bd-sidebar__row - it is not a destination, and
   AppSidebarTests enumerates the navigation by that class), and it carries a .bd-sidebar__label
   so the rail's existing label-clipping makes it icon-only for free.

   The panel is absolutely positioned, so opening it is out of flow. That is not a style
   preference: rendering both chip groups inline costs 316px of a fixed-height column whose
   utility zone does not shrink, and at 1440x900 it pushed the Discover and Account groups off
   the navigation entirely - with every DOM measurement reading fine and only the screenshot
   showing the navigation gone.

   The UA hides a closed <details>'s content two different ways depending on engine - display:
   none on the child in older ones, content-visibility: hidden on ::details-content in current
   Chrome, the pair that shipped the sidebar itself invisible in v0.0.36. Both are neutralised
   unconditionally here, and visibility is decided by `display` on the panel alone: one mechanism
   to reason about rather than two that disagree per browser. */
.bd-sidebar__settings {
  position: relative;
  flex: none;
}

.bd-sidebar__settings::details-content {
  content-visibility: visible;
  block-size: auto;
}

.bd-sidebar__settings-summary {
  display: flex;
  align-items: center;
  gap: var(--bd-space-3);
  min-width: 0;
  min-height: 44px;
  padding-inline: var(--bd-space-3);
  border-radius: var(--bd-radius-md);
  color: var(--bd-color-muted);
  cursor: pointer;
  list-style: none; /* Firefox/Safari draw a disclosure triangle on <summary> by default */
}

.bd-sidebar__settings-summary::-webkit-details-marker,
.bd-sidebar__settings-summary::marker {
  display: none;
  content: "";
}

/* Base/sm - inside the off-canvas drawer the flyout opens upward and stays within the panel's
   own width: anchoring it to the right (left: 100%, as at md+) would put a 240px popover past
   the viewport edge on a 360px phone, which is the horizontal overflow this repository has a
   whole rule about. */
.bd-sidebar__settings-panel {
  display: none;
  position: absolute;
  left: var(--bd-space-2);
  right: var(--bd-space-2);
  bottom: 100%;
  z-index: var(--bd-z-sidebar);
  flex-direction: column;
  gap: var(--bd-space-3);
  margin-bottom: var(--bd-space-2);
  padding: var(--bd-space-3);
  border: 1px solid var(--bd-color-border);
  border-radius: var(--bd-radius-lg);
  background: var(--bd-color-surface-raised);
  box-shadow: var(--bd-shadow-2);
}

.bd-sidebar__settings[open] > .bd-sidebar__settings-panel {
  display: flex;
}

.bd-sidebar__setting {
  display: flex;
  flex-direction: column;
  gap: var(--bd-space-2);
  min-width: 0;
}

.bd-sidebar__setting-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--bd-color-muted);
}

.bd-sidebar__legal-links {
  display: flex;
  flex-direction: column;
}

/* #793 shipped these deliberately iconless and Thimo overruled it on 2026-09-22. The left
   padding that used to reserve the icon column now holds an actual icon-wrap, so the label lands
   where it always did and the glyph costs no width. Only the wrap's HEIGHT is overridden: 40px
   would grow a 32px row, and the utility zone is flex: none, so four of those is 32px taken out
   of the navigation (#746, and the 35 % ceiling SidebarPresentationTests pins). */
.bd-sidebar__legal-link {
  display: flex;
  align-items: center;
  gap: var(--bd-space-3);
  min-height: 32px;
  padding-inline: var(--bd-space-3);
  border-radius: var(--bd-radius-md);
  color: var(--bd-color-muted);
  font-size: var(--bd-type-body-sm-size);
  text-decoration: none;
}

.bd-sidebar__legal-link .bd-sidebar__icon-wrap {
  height: 28px;
}

.bd-sidebar__legal-link .bd-sidebar__icon {
  width: 18px;
  height: 18px;
}

.bd-sidebar__legal-compact {
  display: none; /* only shown in rail mode — see the media blocks above */
  align-items: center;
  gap: var(--bd-space-3);
  min-height: 44px;
  padding-inline: var(--bd-space-3);
  border-radius: var(--bd-radius-md);
  color: var(--bd-color-muted);
  text-decoration: none;
}

/* ---- the icon-rail hover/focus tooltip (issue #456) ----

   display: none UNCONDITIONALLY here, outside any media query, is belt and braces: JS only ever
   toggles the `hidden` attribute (sidebar-tooltip.js), never a display style, so this rule alone
   is what stops the bubble ever painting at any width but the rail even if JS mistakenly clears
   `hidden` somewhere else — the @media (min-width: 905px) block below is the ONE place that turns
   it back on, which is what makes it the single source of truth the JS comment promises.

   It is deliberately NOT nested under .bd-sidebar__utility or .bd-sidebar__scroll — see
   AppSidebar.razor's own remarks on why the markup places it as a direct child of .bd-sidebar
   itself instead. */
.bd-sidebar__tooltip {
  display: none;
}

@media (min-width: 905px) {
  /* No burger, no scrim, no off-canvas drawer any more — the panel is persistent chrome. */
  .bd-sidebar-drawer__summary {
    display: none;
  }

  .bd-sidebar-drawer__scrim {
    display: none !important;
  }

  /* THE LINE BELOW IS NOT ENOUGH ON ITS OWN, AND THAT SHIPPED BROKEN IN v0.0.36.
     Chrome stopped hiding a closed <details>'s content with `display: none` on the
     child. It now wraps that content in a `::details-content` pseudo-element and sets
     `content-visibility: hidden` there. So overriding `display` on .bd-sidebar wins an
     argument nobody is having: the sidebar gets a full layout box — 264px wide,
     `display: flex`, `visibility: visible`, and `content-visibility: visible` on the
     element itself — and is never painted.

     Every probe reports it visible, because every property you would think to check IS
     visible. It was found by looking at a screenshot, after three rounds of measuring
     the DOM said the navigation was fine while the person using the site could see that
     it was not. The suppression is on the pseudo-element, so that is where the override
     has to go. */
  .bd-sidebar-drawer::details-content {
    content-visibility: visible;
    /* The pseudo also clamps block-size while hidden; without this the sidebar lays out
       at full document height (measured: 3,884px against a 900px viewport) instead of
       the fixed viewport column it is meant to be. */
    block-size: auto;
  }

  .bd-sidebar {
    display: flex !important; /* overrides the UA default that hides a closed <details>'s content */
    /* The burger strip is gone at this width, so the column owns the top edge and the safe-area
       inset again - both of which the drawer state hands to the strip instead. */
    top: 0;
    padding-top: var(--bd-safe-area-top);
    width: 72px;
    transform: none;
    transition: none;
  }

  .bd-sidebar__header {
    display: flex;
  }

  /* Rail-mode compaction, unconditional at md (§4.2: "md gets no manual override — at
     this width the rail is the only state"). The labels stay in the DOM — read by every
     screen reader exactly as if visible — they are only clipped, never display:none or
     aria-hidden (the existing BottomNav "never icon-only without a text label" rule,
     carried forward unchanged). The group headings and the wordmark ARE display:none
     here, safely: both are aria-hidden decoration whose accessible name is carried
     elsewhere (the role="group" wrapper's own aria-label; the brand link's own
     aria-label) — see AppSidebar.razor's remarks. */
  .bd-sidebar__label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  .bd-sidebar__group-label,
  .bd-sidebar__brand-label,
  .bd-sidebar__legal-links {
    display: none;
  }

  /* THE RAIL HAS ONE AXIS AND EVERYTHING ON IT SITS ON THAT AXIS (issue #476).
     Reported as "the logo in the top left corner still looks wrong", and measured on production
     at 988px before changing anything: the rail is 72px, so its centre is x=36 - and nothing was
     on it. The brand mark's centre was x=26 and every nav glyph's was x=41, fifteen pixels apart
     from each other and both off the column they appear to belong to.

     Two independent causes, which is why it survived a look at either one alone.
     The brand: .bd-sidebar__header is `justify-content: space-between` with `padding-inline`, and
     in the rail its label and its collapse toggle are both display:none - so the lone 26px glyph
     had nothing to be spaced against and simply sat against the left padding.
     The glyphs: .bd-sidebar__row keeps `padding-inline: var(--bd-space-3)`, which leaves a 30px
     content box inside a 54px row - and .bd-sidebar__icon-wrap is 40px. A 40px box in a 30px
     content box overflows to the right by exactly the 5px measured.

     So the rail zeroes the inline padding it cannot afford and centres both, rather than nudging
     either with a magic offset. */
  .bd-sidebar__header {
    justify-content: center;
    padding-inline: 0;
  }

  /* The settings summary and the compact legal row ride along: neither carries
     .bd-sidebar__row, so neither was centred (#793). */
  .bd-sidebar__row,
  .bd-sidebar__settings-summary,
  .bd-sidebar__legal-compact {
    justify-content: center;
    padding-inline: 0;
  }


  /* The group headings are gone in the rail, so the grouping is carried by a hairline
     instead — otherwise nine icons read as one undifferentiated list. */
  .bd-sidebar__group {
    margin-top: var(--bd-space-2);
    padding-top: var(--bd-space-2);
    border-top: 1px solid var(--bd-color-border);
  }

  .bd-sidebar__legal-compact {
    display: flex;
  }

  /* md and up - the drawer is gone and the panel is a persistent fixed column, so the flyout
     anchors to its right-hand edge instead of opening upward over the navigation.
     .bd-sidebar is position: fixed with no overflow clipping, so it escapes the column. */
  .bd-sidebar__settings-panel {
    left: calc(100% + var(--bd-space-2));
    right: auto;
    bottom: 0;
    width: 240px;
    margin-bottom: 0;
  }

  /* The tooltip bubble itself, shown only at rail width (§4.2 — labels are clipped nowhere
     else, so there is nothing for a tooltip to stand in for at md+/expanded or below md, where
     the drawer already shows full labels). `:not([hidden])` rather than a bare selector: the
     element is always in the DOM (sidebar-tooltip.js finds it once on load), and `display` here
     only overrides the unconditional `display: none` above while JS has cleared `hidden` — the
     un-hidden-but-covered-by-`display:none` state the base rule guards against everywhere else
     is exactly what this selector is careful not to reintroduce.

     position: absolute, not fixed: .bd-sidebar is itself position: fixed, which makes it the
     containing block for an absolutely-positioned descendant (the same mechanism
     .bd-sidebar__settings-panel below relies on, and the same "escapes the column" comment
     applies unchanged) — so this paints outside the 72px rail without being clipped by anything,
     regardless of which row (inside .bd-sidebar__scroll or the never-clipped utility zone)
     triggered it. `top` is set by JS to the hovered/focused row's own vertical midpoint;
     transform: translateY(-50%) is what turns that midpoint into true vertical centering rather
     than the bubble's own top edge landing there.

     pointer-events: none: the bubble must never intercept the hover it is a response to, or
     moving the mouse from a row onto the tooltip covering it would fire this row's own pointerout
     and hide the very thing the pointer is now over.

     z-index: same tier as .bd-sidebar__settings-panel (--bd-z-sidebar) — both are floating
     content anchored to the rail rather than a dropdown/modal a click can open beneath them, so
     there is no ordering requirement between the two, and neither shows while the other's
     triggering gesture is in progress (a settings-panel is a click-opened <details>; a tooltip is
     a hover/focus response) so they cannot occupy the same pixel at the same time regardless. */
  .bd-sidebar__tooltip:not([hidden]) {
    display: block;
    position: absolute;
    left: 100%;
    margin-left: var(--bd-space-2);
    z-index: var(--bd-z-sidebar);
    padding: var(--bd-space-1) var(--bd-space-3);
    border-radius: var(--bd-radius-md);
    background: var(--bd-color-surface-overlay);
    color: var(--bd-color-on-surface);
    box-shadow: var(--bd-shadow-3);
    font-size: var(--bd-type-body-sm-size);
    white-space: nowrap;
    pointer-events: none;
    transform: translateY(-50%);
  }
}

@media (min-width: 1240px) {
  .bd-sidebar {
    width: 264px;
  }

  .bd-sidebar__collapse-toggle {
    display: inline-flex;
  }

  /* Expanded is the default at lg/xl: undo every rail-mode compaction from the md block
     above... */
  .bd-sidebar__label {
    position: static;
    width: auto;
    height: auto;
    padding: 0;
    margin: 0;
    overflow: hidden;
    clip: auto;
    white-space: nowrap;
    text-overflow: ellipsis;
    border: 0;
  }

  .bd-sidebar__group-label,
  .bd-sidebar__brand-label,
  .bd-sidebar__legal-links {
    display: block;
  }

  /* THE RAIL'S CENTRING WAS NEVER UNDONE HERE - #793's FIRST DEFECT (doc 6.2d). This block
     undid every other rail compaction but not that one, so at 264px each icon sat wherever its
     own label's width put it, across 82.6px. */
  .bd-sidebar__header {
    justify-content: space-between;
    padding-inline: var(--bd-space-3);
  }

  .bd-sidebar__row,
  .bd-sidebar__settings-summary {
    justify-content: flex-start;
    padding-inline: var(--bd-space-3);
  }

  /* space-2, not space-4 (#746): four heading blocks and their margins were 64px. */
  .bd-sidebar__group {
    margin-top: var(--bd-space-2);
    padding-top: 0;
    border-top: none;
  }

  .bd-sidebar__legal-compact {
    display: none;
  }

  /* ...unless the visitor collapsed it (AppSidebar.razor's _collapsed state, reflected
     as this class on its own root element) — then lg/xl reads exactly like the md rail
     above, one selector at a time so the two never drift apart. */
  .bd-mobile-topbar--rail .bd-sidebar {
    width: 72px;
  }

  .bd-mobile-topbar--rail .bd-sidebar__label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  .bd-mobile-topbar--rail .bd-sidebar__group-label,
  .bd-mobile-topbar--rail .bd-sidebar__brand-label,
  .bd-mobile-topbar--rail .bd-sidebar__legal-links {
    display: none;
  }

  /* The collapsed lg/xl rail reads exactly like the md rail above - same axis, same reason
     (#476). Stated separately rather than shared, one selector at a time, which is the
     convention this whole block already follows so the two states cannot drift apart. */
  .bd-mobile-topbar--rail .bd-sidebar__header {
    justify-content: center;
    padding-inline: 0;
  }

  .bd-mobile-topbar--rail .bd-sidebar__row,
  .bd-mobile-topbar--rail .bd-sidebar__settings-summary,
  .bd-mobile-topbar--rail .bd-sidebar__legal-compact {
    justify-content: center;
    padding-inline: 0;
  }


  .bd-mobile-topbar--rail .bd-sidebar__group {
    margin-top: var(--bd-space-2);
    padding-top: var(--bd-space-2);
    border-top: 1px solid var(--bd-color-border);
  }

  .bd-mobile-topbar--rail .bd-sidebar__legal-compact {
    display: flex;
  }

}

/* =========================================================================
   TopBar — sticky header (§5). Chrome, rendered through
   <GlassSurface Class="bd-top-bar"> — see the note on BottomNav above; this
   block owns layout only, never background/box-shadow.
   ========================================================================= */

.bd-top-bar {
  position: sticky;
  top: 0;
  z-index: var(--bd-z-sticky-header);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--bd-space-3);
  min-height: 56px;
  padding: var(--bd-space-2) var(--bd-space-4);
  padding-top: calc(var(--bd-space-2) + var(--bd-safe-area-top));
  border-bottom: 1px solid var(--bd-color-border);
}

.bd-top-bar__title {
  font-family: var(--bd-font-display);
  font-weight: 600;
  font-size: var(--bd-type-headline-size);
  line-height: var(--bd-type-headline-line);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bd-top-bar__actions {
  display: flex;
  align-items: center;
  gap: var(--bd-space-1);
  flex: none;
  /* Caller supplies a bounded action fragment. */
}

/* =========================================================================
   Card grid / dense list shells shared by SetCard, PartTile.
   Browse surfaces use the card grid (2 cols at sm, 2-3 at md — §4.1);
   Collection surfaces stay single-column (density beats grid — §1.4).
   ========================================================================= */

.bd-card-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--bd-space-4);
}

@media (min-width: 600px) {
  .bd-card-grid {
    /* minmax(0, 1fr), not a bare 1fr: a bare 1fr track's minimum width is its content's
       intrinsic size, so a set/part/minifig title long enough to be one unbroken run of
       characters would widen the whole grid rather than truncate inside its card -
       BrickDb.App.Shared.Tests.Design.NoHorizontalScrollSourceGuardTests. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 905px) {
  .bd-card-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--bd-space-5);
  }
}

.bd-list {
  display: flex;
  flex-direction: column;
}

.bd-list .bd-part-tile:first-child {
  border-top-left-radius: var(--bd-radius-md);
  border-top-right-radius: var(--bd-radius-md);
}

.bd-list .bd-part-tile:last-child {
  border-bottom-left-radius: var(--bd-radius-md);
  border-bottom-right-radius: var(--bd-radius-md);
  border-bottom: none;
}

/* =========================================================================
   EmptyState (§5) — zero-result search, empty collection, missing-image
   slots (lighter inline variant).
   ========================================================================= */

.bd-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--bd-space-4);
  padding: var(--bd-space-8) var(--bd-space-5);
}

/* The illustration is the one large graphic on an otherwise empty screen, and a grey
   one makes an empty collection read as a broken collection. It takes the brand colour,
   resolved per theme exactly the way app.css resolves link colour (base primary in
   light, primary-light in dark — the base ramp is calibrated for fills, not for text
   or line-art on a dark ground, tokens.css §2.3), on a tinted disc so it reads as a
   deliberate illustration rather than a stray icon. Non-text, ≥3:1 against the section
   surface in both themes. */
.bd-empty-state__icon {
  width: 88px;
  height: 88px;
  padding: var(--bd-space-3);
  border-radius: var(--bd-radius-pill);
  background: var(--bd-color-section-surface);
  color: var(--bd-color-primary);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bd-empty-state__icon {
    color: var(--bd-color-primary-light);
  }
}

:root[data-theme="dark"] .bd-empty-state__icon {
  color: var(--bd-color-primary-light);
}

.bd-empty-state__title,
.bd-confirm-sheet__title,
.bd-alert__title {
  font-family: var(--bd-font-display);
  font-weight: 600;
  font-size: var(--bd-type-title-size);
  line-height: var(--bd-type-title-line);
}

.bd-empty-state__message,
.bd-confirm-sheet__message,
.bd-alert__message {
  font-size: var(--bd-type-body-size);
  line-height: var(--bd-type-body-line);
}

.bd-empty-state__message,
.bd-confirm-sheet__message {
  color: var(--bd-color-muted);
}

.bd-empty-state__message {
  max-width: 40ch;
}

/* Lighter inline variant — used for missing-image slots inside a tile/card
   rather than a full empty screen (design-system.md §7). */
.bd-empty-state--inline {
  padding: var(--bd-space-3);
  gap: var(--bd-space-2);
  background: var(--bd-color-section-surface);
  border-radius: inherit;
  height: 100%;
  justify-content: center;
}

.bd-empty-state--inline .bd-empty-state__icon {
  width: 32px;
  height: 32px;
  padding: 0;
  background: none;
}

.bd-empty-state--inline .bd-empty-state__message {
  font-size: var(--bd-type-caption-size);
  line-height: var(--bd-type-caption-line);
}

/* =========================================================================
   ConfirmSheet (§5) — bottom sheet for destructive/irreversible actions.
   Primary button is the SAFE action; the destructive action is secondary,
   text-styled, in --bd-color-danger — never the other way round.

   The spec doesn't call out a >=sm layout for ConfirmSheet the way it does
   for FilterSheet; a full-width bottom sheet stretched across a 1440px
   viewport reads broken, so this extends the same "sheet becomes a centred
   surface once there's room" idea FilterSheet already uses, capping the
   width and rounding all four corners from `sm` up. See the report.
   ========================================================================= */

.bd-scrim {
  position: fixed;
  inset: 0;
  z-index: var(--bd-z-scrim);
  background: var(--bd-color-scrim);
}

.bd-confirm-sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--bd-z-modal);
  display: flex;
  flex-direction: column;
  gap: var(--bd-space-4);
  width: 100%;
  padding: var(--bd-space-3) var(--bd-space-5)
           calc(var(--bd-space-5) + var(--bd-safe-area-bottom));
  border-radius: var(--bd-radius-xl) var(--bd-radius-xl) 0 0;
  background: var(--bd-color-surface-overlay);
  box-shadow: var(--bd-shadow-4);
  animation: bd-sheet-in var(--bd-duration-medium) var(--bd-ease-emphasized);
}

.bd-confirm-sheet__handle {
  align-self: center;
  width: 36px;
  height: 4px;
  border-radius: var(--bd-radius-pill);
  background: var(--bd-color-border-strong);
}

.bd-confirm-sheet__actions {
  display: flex;
  flex-direction: column;
  gap: var(--bd-space-2);
}

.bd-confirm-sheet__actions .bd-btn {
  width: 100%;
}

@media (min-width: 600px) {
  .bd-confirm-sheet {
    left: 50%;
    right: auto;
    bottom: var(--bd-space-6);
    transform: translateX(-50%);
    width: min(420px, calc(100% - var(--bd-space-6) * 2));
    border-radius: var(--bd-radius-xl);
    padding-bottom: var(--bd-space-5);
  }
}

@media (prefers-reduced-motion: reduce) {
  .bd-confirm-sheet {
    animation: bd-toast-in-reduced var(--bd-duration-instant) linear;
  }
}

/* =========================================================================
   Form controls — text input, select, textarea, field label/hint, toggle
   chip, disclosure, alert banner. The v1 component set (§5) has no entry
   for any of these: everything that existed before this pass was
   read-only display (SetCard, PartTile, ConditionBadge...). The condition
   editor and the add-to-collection form are the first real forms in the
   app, so this is new rather than reused — flagged per the brief. Built
   from the same tokens/rules as everything above (360px-first, >=44px
   targets, no hover-only affordance, visible focus via app.css).
   ========================================================================= */

.bd-field {
  display: flex;
  flex-direction: column;
  gap: var(--bd-space-1);
}

.bd-field__label {
  font-family: var(--bd-font-body);
  font-weight: 600;
  font-size: var(--bd-type-body-sm-size);
  line-height: var(--bd-type-body-sm-line);
  color: var(--bd-color-on-surface);
}

.bd-field__hint {
  font-family: var(--bd-font-body);
  font-size: var(--bd-type-caption-size);
  line-height: var(--bd-type-caption-line);
  color: var(--bd-color-muted);
}

.bd-input,
.bd-select,
.bd-textarea {
  width: 100%;
  min-height: 44px;
  padding: var(--bd-space-2) var(--bd-space-3);
  border-radius: var(--bd-radius-md);
  border: 1px solid var(--bd-color-border);
  background: var(--bd-color-surface-raised);
  color: var(--bd-color-on-surface);
  font-family: var(--bd-font-body);
  font-size: var(--bd-type-body-size);
  line-height: var(--bd-type-body-line);
}

.bd-textarea {
  min-height: 88px;
  resize: vertical;
}

.bd-input:focus,
.bd-select:focus,
.bd-textarea:focus {
  outline: none; /* the visible focus ring below replaces it, per app.css */
}

.bd-input:focus-visible,
.bd-select:focus-visible,
.bd-textarea:focus-visible {
  border-color: var(--bd-color-focus-ring);
}

.bd-form-grid {
  display: flex;
  flex-direction: column;
  gap: var(--bd-space-4);
}

@media (min-width: 600px) {
  .bd-form-grid--cols-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

.bd-checkbox-row {
  display: flex;
  align-items: center;
  gap: var(--bd-space-2);
  min-height: 44px;
}

.bd-checkbox-row input[type="checkbox"] {
  width: 22px;
  height: 22px;
  flex: none;
  accent-color: var(--bd-color-primary);
}

/* Multi-select toggle chips — used for ConditionFlag, which is an open,
   additive set (domain-model.md §4.7): a wrapped row of tappable pills
   beats a scrolling checkbox list at this cardinality. */
.bd-toggle-chip-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bd-space-2);
}

.bd-toggle-chip {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--bd-space-4);
  border-radius: var(--bd-radius-pill);
  border: 1px solid var(--bd-color-border-strong);
  background: var(--bd-color-surface-raised);
  color: var(--bd-color-on-surface);
  font-family: var(--bd-font-body);
  font-weight: 500;
  font-size: var(--bd-type-body-sm-size);
}

/* aria-current is the <a> spelling of aria-pressed, for a chip group made of links
   (the support queue filter, #859). One declaration, not a second copy. */
.bd-toggle-chip[aria-pressed="true"],
.bd-toggle-chip[aria-current="page"] {
  background: var(--bd-color-primary);
  border-color: var(--bd-color-primary);
  color: var(--bd-color-on-primary);
  font-weight: 600;
}

/* Progressive disclosure — a native <details>/<summary> pair so the
   expand/collapse affordance is keyboard- and screen-reader-operable with
   no script. Used to tuck the seven-axis condition editor's less-common
   axes below the fast path (design-system.md brief: "must not feel like a
   tax form"). */
.bd-disclosure {
  border: 1px solid var(--bd-color-border);
  border-radius: var(--bd-radius-md);
  background: var(--bd-color-section-surface);
  overflow: hidden;
}

.bd-disclosure__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--bd-space-2);
  min-height: 44px;
  /* The 4px inset is the accent rule below, not decoration for its own sake: it is what
     marks the row as a section header rather than another form field, at a glance and
     without reading it. */
  padding: var(--bd-space-3) var(--bd-space-4) var(--bd-space-3) var(--bd-space-3);
  border-left: 4px solid var(--bd-color-primary);
  cursor: pointer;
  list-style: none;
  font-family: var(--bd-font-body);
  font-weight: 600;
  font-size: var(--bd-type-body-size);
}

.bd-disclosure__summary::-webkit-details-marker {
  display: none;
}

.bd-disclosure__summary-icon {
  width: 20px;
  height: 20px;
  flex: none;
  color: var(--bd-color-muted);
  transition: transform var(--bd-duration-short) var(--bd-ease-standard);
}

.bd-disclosure[open] .bd-disclosure__summary-icon {
  transform: rotate(180deg);
}

.bd-disclosure__body {
  display: flex;
  flex-direction: column;
  gap: var(--bd-space-4);
  padding: 0 var(--bd-space-4) var(--bd-space-4);
  border-top: 1px solid var(--bd-color-border);
  padding-top: var(--bd-space-4);
}

/* Alert banner — inline error states ("what went wrong", never "something
   went wrong"), with an optional retry action. */
.bd-alert {
  display: flex;
  flex-direction: column;
  gap: var(--bd-space-3);
  padding: var(--bd-space-4);
  border-radius: var(--bd-radius-md);
  background: var(--bd-color-danger-surface);
}

.bd-alert__title {
  color: var(--bd-color-on-surface);
}

.bd-alert__message {
  color: var(--bd-color-on-surface);
}

.bd-alert__actions {
  display: flex;
  gap: var(--bd-space-2);
}

/* ---------------------------- Tables ---------------------------------- */

.bd-table-scroll {
  overflow-x: auto;
  /* Without this the flex/grid parent sizes to the table's intrinsic width
     and the overflow lands on the page instead of in here. */
  min-width: 0;
  max-width: 100%;
  margin: var(--bd-space-3) 0;
}

.bd-table {
  border-collapse: collapse;
  /* Not width:100% — a narrow viewport must be allowed to scroll the table
     rather than crush its columns into unreadable slivers. */
  min-width: 100%;
  font-family: var(--bd-font-body);
  font-size: var(--bd-type-body-sm-size);
  line-height: var(--bd-type-body-sm-line);
  color: var(--bd-color-on-surface);
}

.bd-table th,
.bd-table td {
  text-align: left;
  padding: var(--bd-space-2) var(--bd-space-3);
  border-bottom: 1px solid var(--bd-color-border);
  vertical-align: top;
}

.bd-table th {
  font-weight: 600;
  white-space: nowrap;
}

/* ------------------------------ Footer -------------------------------- */

.bd-footer {
  margin-top: var(--bd-space-8);
  padding: var(--bd-space-5) 0 var(--bd-space-8);
  border-top: 1px solid var(--bd-color-border);
  display: flex;
  flex-direction: column;
  gap: var(--bd-space-3);
  min-width: 0;
}

/* One row for both groups: the About link and the legal <nav> beside it, with the same
   gap between them as between the legal links themselves, so the row reads as one list
   even though it is two elements (see AppFooter.razor on why About is not inside the
   nav). Wraps to as many lines as a narrow viewport needs. */
.bd-footer__links {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--bd-space-2) var(--bd-space-4);
  min-width: 0;
}

.bd-footer__nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bd-space-2) var(--bd-space-4);
}

.bd-footer__link {
  font-family: var(--bd-font-body);
  font-size: var(--bd-type-body-sm-size);
  line-height: var(--bd-type-body-sm-line);
  /* The Impressum link stays an obvious, ordinary link — see AppFooter.razor.
     No hover-to-reveal, no icon-only treatment. */
  color: var(--bd-color-on-surface);
}

.bd-footer__disclaimer {
  font-family: var(--bd-font-body);
  font-size: var(--bd-type-caption-size);
  line-height: var(--bd-type-caption-line);
  color: var(--bd-color-muted);
  max-width: 68ch;
}

/* The running build's own version (AppFooter.razor). Tabular figures so the digits do not
   visibly shift width between builds; muted, like the disclaimer beside it, because this is
   metadata about the deploy rather than something a visitor is meant to read on purpose. */
.bd-footer__version {
  font-family: var(--bd-font-body);
  font-size: var(--bd-type-caption-size);
  line-height: var(--bd-type-caption-line);
  color: var(--bd-color-muted);
  font-variant-numeric: tabular-nums;
}

/* ABOVE THE BOTTOM BAR, NOT BEHIND IT (navigation.md §2.4, design-system.md's rule that a
   toast clears whatever chrome is on screen). Below 905px that is the bar's 64px plus the
   inset; at 905px and above the bar is gone and the offset returns to what it was. The
   override below is a real one for the same reason .bd-app-shell__main's is: this base rule
   is unconditional, so omitting it would float every desktop toast 64px off the floor. */
.bd-toast-region {
  position: fixed;
  right: var(--bd-space-4);
  bottom: calc(var(--bd-space-4) + 64px + var(--bd-safe-area-bottom));
  left: var(--bd-space-4);
  z-index: var(--bd-z-toast);
  pointer-events: none;
}

@media (min-width: 905px) {
  .bd-toast-region {
    bottom: calc(var(--bd-space-4) + var(--bd-safe-area-bottom));
  }
}

.bd-toast {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--bd-space-2);
  max-width: 36rem;
  margin-inline: auto;
  padding: var(--bd-space-3);
  border: 1px solid var(--bd-color-border-strong);
  border-radius: var(--bd-radius-lg);
  background: var(--bd-color-surface-overlay);
  box-shadow: var(--bd-shadow-3);
  pointer-events: auto;
}

.bd-toast__severity {
  font-size: var(--bd-type-caption-size);
  font-weight: 700;
}

.bd-toast--success .bd-toast__severity { color: var(--bd-color-success); }
.bd-toast--warning .bd-toast__severity { color: var(--bd-color-warning); }
.bd-toast--error .bd-toast__severity { color: var(--bd-color-danger); }

/* min-width: 0 is the shell's own rule applied here — without it a long
   e-mail address refuses to shrink in a flex row and pushes the page
   sideways at 360px. */
[data-account-identity] > .bd-stack {
    min-width: 0;
    overflow-wrap: anywhere;
}


@keyframes bd-sheet-in {
  from { opacity: 0; transform: translateY(24px); }
  to { opacity: 1; transform: translateY(0); }
}

@media (min-width: 600px) {
  @keyframes bd-sheet-in {
    from { opacity: 0; transform: translate(-50%, 24px); }
    to { opacity: 1; transform: translate(-50%, 0); }
  }}
