/* servicedesk app styles — Blueprint token-only (no raw hex, no custom radii,
 * no gradients). Layout glue and EasyMDE ↔ Blueprint reconciliation. */

/* inline forms (e.g. the logout button in the nav) */
.sd-inline { display: inline; }
/* Horizontal tab menu at the top of /admin/settings (admin_settings.html):
 * each tab is a real link (bp-btn), so it works with JS disabled. */
.sd-tabs { display: flex; flex-wrap: wrap; gap: var(--bp-space-2); margin-bottom: var(--bp-space-4); }
/* Long user-agent strings must not stretch the sessions table. */
.sd-truncate { display: inline-block; max-width: 22rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }

/* neutral status pill (spec §9 "waiting"): blueprint-components.css defines
 * no --neutral pill variant, so the app supplies it — token-only. */
.bp-pill--neutral {
  background-color: var(--bp-surface-2);
  color: var(--bp-ink-dim);
  border-color: var(--bp-line);
}

/* compact select for dense table rows (mirrors the blueprint sm-control
 * pattern: fixed narrow width like .bp-input--sm, compact padding/font). */
.bp-select--sm {
  width: 140px;
  padding: var(--bp-space-1) var(--bp-space-2);
  font-size: var(--bp-text-2xs);
}

/* desk queue bulk bar: a select and its action button share one line inside
 * the field, so the button keeps its own (content) width and reads as an
 * action instead of stretching into a second stacked input box. The shared
 * resolution textarea takes a full row of its own: the toolbar bottom-aligns
 * its fields, so a multi-line control sharing the action row would push the
 * selects and buttons down away from their labels. */
/* A toolbar revealed by selection (the bulk bar) must honour its `hidden`
 * attribute: .bp-toolbar's display:flex is an author rule and so beats the
 * browser's own [hidden] { display: none }, which left the bulk bar — and its
 * action buttons — on screen with nothing selected. */
.bp-toolbar[hidden] { display: none; }

.sd-bulk-action {
  display: flex;
  align-items: center;
  gap: var(--bp-space-2);
}
.sd-bulk-action .bp-btn { flex: none; white-space: nowrap; }
.sd-bulk-resolution { flex: 1 0 100%; }
.sd-bulk-resolution .bp-textarea { min-height: 0; }

/* EasyMDE: align the vendored editor with Blueprint surfaces + tokens */
.EasyMDEContainer .CodeMirror {
  color: var(--bp-ink);
  background-color: var(--bp-surface);
  border: var(--bp-border-width) solid var(--bp-line-strong);
  border-radius: var(--bp-radius-sm);
  font-family: var(--bp-font-mono);
  font-size: var(--bp-text-base);
}
.EasyMDEContainer .CodeMirror-cursor { border-color: var(--bp-ink); }
.EasyMDEContainer .editor-toolbar {
  border: var(--bp-border-width) solid var(--bp-line);
  border-radius: var(--bp-radius-sm);
  background-color: var(--bp-surface-2);
}
.EasyMDEContainer .editor-toolbar button { color: var(--bp-ink-dim); }
.EasyMDEContainer .editor-toolbar button:hover,
.EasyMDEContainer .editor-toolbar button.active {
  background-color: var(--bp-surface);
  border-color: var(--bp-line-strong);
  color: var(--bp-ink);
}
.EasyMDEContainer .editor-preview {
  color: var(--bp-ink);
  background-color: var(--bp-surface);
}

/* public new-ticket form: space above the submit row */
.sd-form-actions { margin-top: var(--bp-space-4); }

/* admin settings: per-field validation error + reset/clear action row */
.sd-field-error { color: var(--bp-crit); }
.sd-field-actions {
  display: flex;
  gap: var(--bp-space-2);
  margin-top: var(--bp-space-1);
}

/* admin settings: AI model suggestion (2026-08-22 ai-backends plan, task 8/9;
   final-fix-wave finding 6 — the spec claims the two bases are visually
   distinguishable, but until this rule existed that rested on the wording
   alone). All three sd-suggest--{{.SuggestionBasis}} variants share the base
   layout; the modifier only changes the left border and its color, so the
   distinction reads even to a viewer who only glances at the shape of the
   line, not just its text.
   - metadata: the provider's own reported field — the accent border reads as
     the more confident, verifiable basis.
   - namematch: an unverified name-pattern guess — a dashed warn-colored
     border keeps it visually weaker than metadata, never mistakable for it.
   - none: no suggestion at all — dim and borderless, matching the muted
     treatment already used for other empty/inapplicable states (.sd-from,
     .sd-stat__unknown). */
.sd-suggest {
  margin-top: var(--bp-space-1);
  padding-left: var(--bp-space-2);
  border-left: 2px solid transparent;
}
.sd-suggest--metadata { border-left-color: var(--bp-accent); }
.sd-suggest--namematch { border-left-style: dashed; border-left-color: var(--bp-warn); }
.sd-suggest--none { color: var(--bp-ink-dim); border-left: none; padding-left: 0; }

/* ── Phase 5: list search bar, result snippets, draft notice ──────────────── */
/* The search input takes the free space next to its button. */
.bp-field--grow { flex: 1 1 auto; }
.sd-searchbar { margin-bottom: var(--bp-space-3); }
/* Requester address under the subject in the queue; break-anywhere so a long
   address can never widen the column past the viewport. */
.sd-from { color: var(--bp-ink-dim); overflow-wrap: anywhere; }
/* CMDB import conflict panel: the side-by-side comparison marks the line
   whose value collides. Token-based, so both themes keep contrast. */
.sd-conflict-table { margin-bottom: var(--bp-space-3); }
.sd-conflict-line th, .sd-conflict-line td { background-color: var(--bp-surface-2); font-weight: 600; }
/* Search snippet under a result row. */
.sd-snippet { color: var(--bp-ink-dim); }
.sd-snippet mark { background-color: var(--bp-surface-2); color: var(--bp-ink); }
/* Restored-draft notice under a markdown editor. */
.sd-draft-note { color: var(--bp-ink-dim); }

/* Dashboard (§3.2): real counts, each drillable into the queue view it counts. */
.sd-stats { display: flex; flex-wrap: wrap; gap: var(--bp-space-3); margin-bottom: var(--bp-space-4); }
.sd-stat { display: flex; flex-direction: column; gap: var(--bp-space-1);
           padding: var(--bp-space-3); min-width: 9rem; text-decoration: none; color: inherit; }
.sd-stat__value { font-size: var(--bp-text-xl); }
.sd-stat__unknown { color: var(--bp-ink-dim); }
.sd-attention { display: grid; grid-template-columns: 1fr; gap: var(--bp-space-4); }
@media (min-width: 60rem) { .sd-attention { grid-template-columns: 1fr 1fr; } }

/* Keyboard cursor row (§3.3). It is a focus target, not only a colour: the
   row carries tabindex="-1" and is focused, so assistive technology follows
   the selection. */
.bp-table tr.is-cursor { outline: var(--bp-border-width) solid var(--bp-accent); outline-offset: -1px; }
.bp-table tr.is-cursor:focus { outline-color: var(--bp-accent); }
#sd-shortcuts-body > div { display: flex; gap: var(--bp-space-3); align-items: baseline; margin-bottom: var(--bp-space-1); }
#sd-shortcuts-body kbd { min-width: 2rem; text-align: center; }

/* Attachment thumbnails (§3.6). The image itself comes from the preview route,
   which serves only a SNIFFED allowlisted image type under a sandboxing CSP —
   a file that merely claims to be an image renders as a broken slot here. */
.sd-attach-grid { display: flex; flex-wrap: wrap; gap: var(--bp-space-2); }
.sd-thumb { max-width: 12rem; max-height: 12rem; }

/* Drag-and-drop upload zone (§3.6). Progressive enhancement around the real
   file input, which is never hidden from assistive technology. */
.sd-dropzone { border: 1px dashed var(--bp-line); padding: var(--bp-space-4); }
.sd-dropzone--over { border-color: var(--bp-accent); }

/* §3.7 — the phone pass. One markup, two layouts: the queue table becomes a
   stack of cards whose labels are rendered from the same data-label the
   desktop header row already carries, so nothing is duplicated in the
   template. Tokens only, no raw colour values. */
.sd-navdrop > summary { display: none; }

/* A control that exists only for browsers without JavaScript: the submit
   button next to a checkbox that submits itself. The root carries sd-js from
   the pre-paint script, so with scripting ON this is hidden before it can
   flash, and with scripting OFF it is simply there. */
.sd-js .sd-nojs-only { display: none; }

/* The ungrouped links above the disclosures: the landing page and the people
   directory. A little air below separates them from the first group without
   pretending to be a group header. */
.sd-navtop { margin-bottom: var(--bp-space-3); }

/* Sidebar groups as disclosures.
   Twenty entries in one flat column meant the eleven administration links sat
   between the daily work and everything else. The groups now collapse, and the
   order runs most-used first with administration last.
   The caret is drawn in CSS rather than added to the markup: it is pure
   decoration next to a real label, so it has no business in the document. It
   inherits currentColor and turns with the group's state. */
.sd-navgroup > summary {
  display: flex;
  align-items: center;
  gap: var(--bp-space-2);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.sd-navgroup > summary::-webkit-details-marker { display: none; }
.sd-navgroup > summary:hover { color: var(--bp-ink); }
.sd-navgroup > summary::after {
  content: "";
  margin-left: auto;
  width: 5px;
  height: 5px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transform-origin: center;
  transition: transform 120ms ease;
  opacity: 0.7;
}
.sd-navgroup[open] > summary::after { transform: rotate(45deg); }
/* The label takes the remaining width so a long entry wraps against the
   sidebar edge instead of pushing its icon out of the row. */
.sd-navgroup__label { min-width: 0; }
.sd-bulk-count { margin: 0; }

@media (max-width: 720px) {
  .bp-app { grid-template-columns: 1fr; }
  .sd-navdrop > summary { display: list-item; padding: var(--bp-space-2) 0; cursor: pointer; }
  /* The sidebar is full-width on a phone, so a caret pushed to the far edge
     ends up hundreds of pixels from the label it belongs to. Below the
     breakpoint it follows the text instead. */
  .sd-navgroup > summary::after { margin-left: var(--bp-space-2); }
  .sd-queue-table thead { display: none; }
  .sd-queue-table tr { display: block; border-bottom: 1px solid var(--bp-line); padding: var(--bp-space-2) 0; }
  .sd-queue-table td { display: flex; justify-content: space-between; gap: var(--bp-space-2); border: 0; }
  .sd-queue-table td::before { content: attr(data-label); color: var(--bp-ink-dim); }
  .sd-bulkbar { position: sticky; bottom: 0; background: var(--bp-surface);
                border-top: 1px solid var(--bp-line); padding: var(--bp-space-2); display: block; }
  .sd-bulkbar .bp-field { display: block; margin-bottom: var(--bp-space-2); }
  .sd-bulkbar .bp-btn,
  .sd-queue-table .bp-checkbox { min-height: 44px; min-width: 44px; }
}

/* SLA indicator (§1.5). A ticket no policy covers renders no pill at all — the
   real-data rule — so there is deliberately no "unknown" pill variant here. */
.sd-sla { white-space: nowrap; }

/* ── Public background (internal/branding) ────────────────────────────────
   The image is served from a directory on the server and can be exchanged at
   runtime; the two URLs arrive as --sd-bg-light / --sd-bg-dark on the public
   shell element, so this rule is the ONLY place that decides which theme gets
   which. The desk shell (.bp-app) never carries .sd-bg.

   It is painted by a fixed pseudo-element rather than as the shell's own
   background: the shell is a centred 960px column, so painting on it would
   show the image in a strip down the middle of an otherwise empty canvas.
   z-index:-1 puts it behind the page content but above the canvas colour
   (the body's background propagates to the canvas, which paints below it).

   There is NO filter, tint or overlay on the image. Content sits on Blueprint
   surface panels, which are opaque, so text contrast is the panel's, not the
   photograph's. */
.sd-bg::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: var(--sd-bg-light, none);
  background-size: cover;
  background-position: center center;
  background-repeat: no-repeat;
}

/* Dark theme: the operator's dark variant, or — when they supplied none —
   `none`, and the theme keeps its own surface. A dimmed or inverted rendering
   of the light image would be a guess, and a muddy one. */
[data-theme="dark"] .sd-bg::before { background-image: var(--sd-bg-dark, none); }

/* Reduced motion is unaffected (nothing animates here), but a very small
   viewport gets the image anchored to the top so the focal area survives the
   crop. */
@media (max-width: 720px) {
  .sd-bg::before { background-position: center top; }
}

/* ── Keeping the content clear of the picture ─────────────────────────────
   The column stays CENTRED and the picture stays full-bleed. What separates
   them is a surface under the content: the text sits on its own sheet, with a
   margin of clear space around it, and the picture shows past its edges.

   Two approaches were tried against a real logo and rejected, so they are not
   tried again:

   - Moving the column to one edge. It dodges the picture but puts the reading
     position against the side of the screen, and the reading position is not
     a thing to give up for a decoration.
   - Painting the image only in the gutter beside the column. It depends
     entirely on how the image is composed: a logo sitting on a wide canvas
     scales to almost nothing inside a narrow band, because `contain` fits the
     canvas, not the subject.

   A sheet works for any image at any width, and needs to know nothing about
   what the picture contains. */
.sd-bg {
  background-color: var(--bp-surface);
  border: var(--bp-border-width) solid var(--bp-line);
  border-radius: var(--bp-radius-md);
  /* A real shadow, not a flat ring of the surface colour. The ring was the
     same colour as the page, so the card had no edge and the picture looked
     CLIPPED by something invisible instead of passing behind a card. The
     border and the shadow are what make the distance legible. */
  box-shadow: 0 2px 16px rgb(0 0 0 / 10%);
  margin-top: var(--bp-space-8);
  margin-bottom: var(--bp-space-10);
}

/* On a phone the sheet would cover the whole screen, so its rounding and its
   outer ring buy nothing and only cost the picture. */
@media (max-width: 720px) {
  .sd-bg { border-radius: 0; box-shadow: none; margin-top: 0; }
}

/* ------------------------------------------------------------------------ *
 * MARKDOWN EDITOR TOOLBAR ICONS
 *
 * EasyMDE draws its toolbar as <i class="fa fa-bold">, i.e. FontAwesome icon
 * classes. FontAwesome was never vendored here — the app ships easymde.min.js
 * and easymde.min.css and nothing else — so every button rendered as an empty
 * box: the controls worked, they were simply invisible.
 *
 * Rather than pull in a whole icon font for twelve glyphs, the icons are drawn
 * here on the SAME 16-unit grid and 1.5 stroke as the sidebar set
 * (internal/app/navicons.go), so the two read as one vocabulary.
 *
 * They are MASKS, not background images, so the glyph takes its colour from
 * the button — which means EasyMDE's own hover, active and disabled states
 * keep working without a single override.
 *
 * A button whose icon is missing here would be invisible again, so
 * TestEveryEasyMDEToolbarIconHasAGlyph reads the vendored bundle and fails if
 * one appears that this file does not draw.
 * ------------------------------------------------------------------------ */
.editor-toolbar i.fa {
  display: inline-block;
  width: 15px;
  height: 15px;
  vertical-align: -2px;
  background-color: currentColor;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: 15px 15px;
  mask-size: 15px 15px;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.editor-toolbar i.fa-bold { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M5.5%203v10'/%3E%3Cpath%20d='M5.5%203h3.4a2.5%202.5%200%200%201%200%205H5.5'/%3E%3Cpath%20d='M5.5%208h4a2.5%202.5%200%200%201%200%205H5.5'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M5.5%203v10'/%3E%3Cpath%20d='M5.5%203h3.4a2.5%202.5%200%200%201%200%205H5.5'/%3E%3Cpath%20d='M5.5%208h4a2.5%202.5%200%200%201%200%205H5.5'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-italic { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M10.8%203h-3.2'/%3E%3Cpath%20d='M8.4%2013H5.2'/%3E%3Cpath%20d='M9.4%203%206.6%2013'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M10.8%203h-3.2'/%3E%3Cpath%20d='M8.4%2013H5.2'/%3E%3Cpath%20d='M9.4%203%206.6%2013'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-header { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M4%203.5v9'/%3E%3Cpath%20d='M11%203.5v9'/%3E%3Cpath%20d='M4%208h7'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M4%203.5v9'/%3E%3Cpath%20d='M11%203.5v9'/%3E%3Cpath%20d='M4%208h7'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-heading { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M4%203.5v9'/%3E%3Cpath%20d='M11%203.5v9'/%3E%3Cpath%20d='M4%208h7'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M4%203.5v9'/%3E%3Cpath%20d='M11%203.5v9'/%3E%3Cpath%20d='M4%208h7'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-quote-left { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M3%204v8'/%3E%3Cpath%20d='M6%205h7'/%3E%3Cpath%20d='M6%208h7'/%3E%3Cpath%20d='M6%2011h4'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M3%204v8'/%3E%3Cpath%20d='M6%205h7'/%3E%3Cpath%20d='M6%208h7'/%3E%3Cpath%20d='M6%2011h4'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-list-ul { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M3%204.5h.01'/%3E%3Cpath%20d='M3%208h.01'/%3E%3Cpath%20d='M3%2011.5h.01'/%3E%3Cpath%20d='M6%204.5h7'/%3E%3Cpath%20d='M6%208h7'/%3E%3Cpath%20d='M6%2011.5h7'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M3%204.5h.01'/%3E%3Cpath%20d='M3%208h.01'/%3E%3Cpath%20d='M3%2011.5h.01'/%3E%3Cpath%20d='M6%204.5h7'/%3E%3Cpath%20d='M6%208h7'/%3E%3Cpath%20d='M6%2011.5h7'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-list-ol { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M6%204.5h7'/%3E%3Cpath%20d='M6%208h7'/%3E%3Cpath%20d='M6%2011.5h7'/%3E%3Cpath%20d='M2.3%203.7h.9v2.3'/%3E%3Cpath%20d='M2.2%208.1h1.5L2.2%2010.2h1.6'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M6%204.5h7'/%3E%3Cpath%20d='M6%208h7'/%3E%3Cpath%20d='M6%2011.5h7'/%3E%3Cpath%20d='M2.3%203.7h.9v2.3'/%3E%3Cpath%20d='M2.2%208.1h1.5L2.2%2010.2h1.6'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-link { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M6.5%209.5%209.5%206.5'/%3E%3Cpath%20d='M7.6%204.9%209%203.4a2.4%202.4%200%200%201%203.4%203.4l-1.5%201.4'/%3E%3Cpath%20d='M8.4%2011.1%207%2012.6a2.4%202.4%200%200%201-3.4-3.4l1.5-1.4'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M6.5%209.5%209.5%206.5'/%3E%3Cpath%20d='M7.6%204.9%209%203.4a2.4%202.4%200%200%201%203.4%203.4l-1.5%201.4'/%3E%3Cpath%20d='M8.4%2011.1%207%2012.6a2.4%202.4%200%200%201-3.4-3.4l1.5-1.4'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-image { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Crect%20x='2.5'%20y='3.5'%20width='11'%20height='9'%20rx='1'/%3E%3Cpath%20d='M2.6%2010.4%206%207l2.5%202.5L11%207l2.4%202.4'/%3E%3Ccircle%20cx='5.6'%20cy='6'%20r='.9'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Crect%20x='2.5'%20y='3.5'%20width='11'%20height='9'%20rx='1'/%3E%3Cpath%20d='M2.6%2010.4%206%207l2.5%202.5L11%207l2.4%202.4'/%3E%3Ccircle%20cx='5.6'%20cy='6'%20r='.9'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-eye { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M1.6%208S4%204.2%208%204.2%2014.4%208%2014.4%208%2012%2011.8%208%2011.8%201.6%208%201.6%208z'/%3E%3Ccircle%20cx='8'%20cy='8'%20r='1.8'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M1.6%208S4%204.2%208%204.2%2014.4%208%2014.4%208%2012%2011.8%208%2011.8%201.6%208%201.6%208z'/%3E%3Ccircle%20cx='8'%20cy='8'%20r='1.8'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-columns { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Crect%20x='2.5'%20y='3.5'%20width='11'%20height='9'%20rx='1'/%3E%3Cpath%20d='M8%203.5v9'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Crect%20x='2.5'%20y='3.5'%20width='11'%20height='9'%20rx='1'/%3E%3Cpath%20d='M8%203.5v9'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-arrows-alt { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M6%202.5H2.5V6'/%3E%3Cpath%20d='M10%202.5h3.5V6'/%3E%3Cpath%20d='M6%2013.5H2.5V10'/%3E%3Cpath%20d='M10%2013.5h3.5V10'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M6%202.5H2.5V6'/%3E%3Cpath%20d='M10%202.5h3.5V6'/%3E%3Cpath%20d='M6%2013.5H2.5V10'/%3E%3Cpath%20d='M10%2013.5h3.5V10'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-question-circle { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Ccircle%20cx='8'%20cy='8'%20r='5.5'/%3E%3Cpath%20d='M6.4%206.4a1.7%201.7%200%200%201%203.2.5c0%201.2-1.6%201.4-1.6%202.4'/%3E%3Cpath%20d='M8%2011.7v.01'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Ccircle%20cx='8'%20cy='8'%20r='5.5'/%3E%3Cpath%20d='M6.4%206.4a1.7%201.7%200%200%201%203.2.5c0%201.2-1.6%201.4-1.6%202.4'/%3E%3Cpath%20d='M8%2011.7v.01'/%3E%3C/svg%3E"); }

/* The bundle also ships these buttons; they are not on the default toolbar,
   but drawing them now means switching one on can never reintroduce a blank
   button. The test below reads the bundle, so this list cannot fall behind. */
.editor-toolbar i.fa-code { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M6%204%202.5%208%206%2012'/%3E%3Cpath%20d='M10%204%2013.5%208%2010%2012'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M6%204%202.5%208%206%2012'/%3E%3Cpath%20d='M10%204%2013.5%208%2010%2012'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-eraser { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M9%203.5%203.4%209.1a1.2%201.2%200%200%200%200%201.7l1.8%201.7h3l5-5a1.2%201.2%200%200%200%200-1.7L11%203.5a1.4%201.4%200%200%200-2%200z'/%3E%3Cpath%20d='M13.5%2012.5h-5'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M9%203.5%203.4%209.1a1.2%201.2%200%200%200%200%201.7l1.8%201.7h3l5-5a1.2%201.2%200%200%200%200-1.7L11%203.5a1.4%201.4%200%200%200-2%200z'/%3E%3Cpath%20d='M13.5%2012.5h-5'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-minus { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M2.5%208h11'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M2.5%208h11'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-repeat { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M13%206.5H6.2A3.2%203.2%200%201%200%206.2%2013H8'/%3E%3Cpath%20d='M10.5%204%2013%206.5%2010.5%209'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M13%206.5H6.2A3.2%203.2%200%201%200%206.2%2013H8'/%3E%3Cpath%20d='M10.5%204%2013%206.5%2010.5%209'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-strikethrough { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M2.5%208h11'/%3E%3Cpath%20d='M11%205.3A3%203%200%200%200%208.2%203.6c-1.8%200-3%20.9-3%202.1%200%201%20.7%201.6%202%202'/%3E%3Cpath%20d='M5.4%2010.4c.3%201.4%201.4%202%203%202%201.9%200%203-1%203-2.3%200-.6-.2-1-.7-1.4'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M2.5%208h11'/%3E%3Cpath%20d='M11%205.3A3%203%200%200%200%208.2%203.6c-1.8%200-3%20.9-3%202.1%200%201%20.7%201.6%202%202'/%3E%3Cpath%20d='M5.4%2010.4c.3%201.4%201.4%202%203%202%201.9%200%203-1%203-2.3%200-.6-.2-1-.7-1.4'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-table { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Crect%20x='2.5'%20y='3.5'%20width='11'%20height='9'%20rx='1'/%3E%3Cpath%20d='M2.5%206.8h11'/%3E%3Cpath%20d='M6.2%206.8v5.7'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Crect%20x='2.5'%20y='3.5'%20width='11'%20height='9'%20rx='1'/%3E%3Cpath%20d='M2.5%206.8h11'/%3E%3Cpath%20d='M6.2%206.8v5.7'/%3E%3C/svg%3E"); }
.editor-toolbar i.fa-undo { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M3%206.5h6.8A3.2%203.2%200%201%201%209.8%2013H8'/%3E%3Cpath%20d='M5.5%204%203%206.5%205.5%209'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20fill='none'%20stroke='%23000'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M3%206.5h6.8A3.2%203.2%200%201%201%209.8%2013H8'/%3E%3Cpath%20d='M5.5%204%203%206.5%205.5%209'/%3E%3C/svg%3E"); }

/* ------------------------------------------------------------------------ *
 * REPORTS — charts, their tables, and paper (§6, §8)
 *
 * Blueprint tokens only, like everything above: an SVG mark is filled from
 * var(--bp-series-N) or from the status ramp, never from a hex value, so the
 * validated palette lives in ONE file (static/blueprint-tokens.css) and a
 * re-skin remaps tokens rather than charts. internal/report/svg.go cannot
 * emit a literal colour at all — Paint is a closed set of token names — so
 * this stylesheet is the only place a chart colour could go wrong, and it
 * names none.
 *
 * The charts are inline SVG rendered on the server. There is no chart library,
 * no canvas and no script: the constraint the CMDB and change modules shipped
 * under, and the reason nothing here needs a JavaScript hook.
 * ------------------------------------------------------------------------ */

.sd-report__section { margin: var(--bp-space-6) 0 var(--bp-space-3); }
.sd-report__covering { margin-bottom: var(--bp-space-1); }
.sd-report__subhead { margin: var(--bp-space-4) 0 var(--bp-space-2); }
.sd-report-panel { margin-bottom: var(--bp-space-4); }

/* Every explanatory line under a figure — what the denominator is, which
   population a median covers, why a count is a floor. They are quieter than
   the figure but they are not decoration, so they keep a readable ink and are
   never hidden (see the print block below). */
.sd-report__note { color: var(--bp-ink-dim); margin: var(--bp-space-2) 0 0; }

/* The honest-failure state, everywhere it appears: a figure that could not be
   counted. It reads as dimmed text rather than as a number, which is the whole
   point — it must not be mistaken for a measurement. There is deliberately no
   red or warning treatment: a failed query is not a bad score. */
.sd-report__unknown { color: var(--bp-ink-dim); }

/* Two panels side by side above 60rem, the same breakpoint the dashboard's
   attention lists use, so the two report pages agree about when a second
   column is affordable. */
.sd-report__cols { display: grid; grid-template-columns: 1fr; gap: var(--bp-space-4); }
@media (min-width: 60rem) { .sd-report__cols { grid-template-columns: 1fr 1fr; } }

.sd-report-stats { margin-bottom: var(--bp-space-3); }

/* The chart itself. report.openSVG emits class="sd-chart" with a viewBox and
   width="100%", so the drawing scales with the panel and never needs a
   measured pixel height — which is what lets it print at whatever width the
   paper gives it. */
.sd-chart-figure { margin: 0 0 var(--bp-space-3); }
.sd-chart { display: block; width: 100%; height: auto; }

/* The table under a chart is REQUIRED (§8) and is the reason a period of 365
   days does not make this page unusable: it is capped in height and scrolls
   inside the panel instead of pushing the next panel a screen and a half away.
   The cap is on the SCROLLER, never on the data — every row is in the document
   and reachable by keyboard, and the print block below removes the cap so a
   printed report carries the whole table. Hiding rows to make a page shorter
   would be the same lie as rounding a figure to make it look better. */
.sd-chart-scroll { max-height: 24rem; overflow: auto; }

/* Wide report tables get the same phone treatment the queue table has: the
   header row disappears and each row becomes a card whose cells are labelled
   from the data-label the desktop header already supplies, so nothing is
   duplicated in the template. Numeric cells lose their right alignment here —
   in a label/value row the value belongs next to its label. */
@media (max-width: 720px) {
  .sd-report-table thead { display: none; }
  .sd-report-table tr { display: block; border-bottom: 1px solid var(--bp-line); padding: var(--bp-space-2) 0; }
  .sd-report-table td { display: flex; justify-content: space-between; gap: var(--bp-space-2); border: 0; text-align: left; }
  .sd-report-table td::before { content: attr(data-label); color: var(--bp-ink-dim); }
  .sd-report__periods .bp-btn { min-height: 44px; }
  /* A capped scroller inside an already-scrolling page is two scrollbars in
     the same gesture on a touch screen. On a phone the table simply runs. */
  .sd-chart-scroll { max-height: none; overflow: visible; }
}

/* ?print=1 is the print layout shown ON SCREEN, so a reader can see what will
   come out of the printer before spending paper on it. It drops exactly what
   print drops: the desk shell's sidebar and this page's own controls.

   The sidebar lives in base.html, outside this page's template, and is reached
   from here with :has() rather than by teaching the shared shell a report-only
   flag — a layout switch that belongs to one page has no business in the shell
   every page renders through. Where :has() is unsupported the preview simply
   keeps its sidebar; nothing is hidden or falsified by that. */
.bp-root:has(.sd-report--print) .bp-sidebar { display: none; }
.bp-root:has(.sd-report--print) .bp-app { display: block; }
.sd-report--print .sd-report__controls { display: none; }

@media print {
  /* Paper is white whatever theme the screen was in, and this app defaults to
     dark. Browsers drop background colours when printing but keep text
     colours, so a dark-theme reader would otherwise print pale grey ink on
     white paper and read nothing at all.
     The remap is to the CSS SYSTEM colours, not to hex values: this file is
     token-only, and Canvas/CanvasText are exactly "whatever this medium's
     paper and ink are". The six series tokens keep their values — each is a
     mid-tone hue that reads on white — and the table under every chart is the
     relief for any reader they do not serve. */
  :root,
  :root[data-theme="dark"],
  :root[data-theme="light"] {
    --bp-ink:          CanvasText;
    --bp-ink-dim:      CanvasText;
    --bp-ink-faint:    CanvasText;
    --bp-surface:      Canvas;
    --bp-surface-2:    Canvas;
    --bp-surface-3:    Canvas;
    --bp-line:         GrayText;
    --bp-grid-line:    GrayText;
  }

  /* Navigation and interactive controls are ink spent on nothing: a printed
     link cannot be clicked. The period switcher, the export offers, the
     sidebar, the flash strip and the two dialogs the shell always renders all
     go.
     WHAT DOES NOT GO: .sd-report__note and .sd-report__unknown. A printed
     report that silently dropped "could not be loaded" would hand somebody a
     page of figures with a hole in it and no way to see the hole — the exact
     failure every state in this module exists to prevent.

     The shell-level rules are global because they are true of printing
     anything — a sidebar and an open dialog are screen furniture on every
     page. The rest is scoped to .sd-report: this task owns the report's print
     layout, not every other page's, and a filter toolbar dropped from a
     printed queue is a decision for whoever prints a queue. */
  .bp-sidebar,
  .bp-topnav,
  .sd-navdrop,
  .bp-modal,
  .bp-drawer,
  .bp-drawer-scrim,
  .bp-toasts { display: none; }

  .sd-report__controls,
  .sd-report .bp-toolbar,
  .sd-report .bp-pager,
  .sd-report .bp-page-head .bp-btn { display: none; }

  .bp-app { display: block; }
  .bp-content { padding: 0; }

  /* Charts and tables are the report, so both survive at full size. The
     scroller's cap is a screen affordance and would truncate the paper copy. */
  .sd-chart-scroll { max-height: none; overflow: visible; }

  /* Panels are not broken across pages where they fit on one, and a table that
     genuinely cannot fit repeats its header on every page it continues onto —
     a column of numbers with no headings on page three is unreadable. A row is
     never split down the middle. */
  .sd-report-panel,
  .sd-chart-figure { break-inside: avoid; page-break-inside: avoid; }
  .sd-report-table thead { display: table-header-group; }
  .sd-report-table tr { break-inside: avoid; page-break-inside: avoid; }

  /* A section heading at the foot of a page with its panel overleaf reads as
     an empty section. */
  .sd-report__section { break-after: avoid; page-break-after: avoid; }
}

/* The object picker (partials/picker.html, static/picker.js). It needs a
 * dropdown that overlays the page instead of pushing content down while
 * open, a capped/scrolling list, hover/focus on rows, and a secondary-styled
 * chosen caption — and blueprint-components.css already has exactly that
 * shape under a different name: .bp-combobox is "a suggestion list anchored
 * under a .bp-input", which is this component. So picker.html composes it
 * from existing pieces (.bp-field for the label/input column, .bp-combobox
 * for the positioning + list + row hover/focus, .bp-small for the secondary
 * caption) rather than re-declaring the same rules under sd- names here.
 * sd-picker / sd-picker__list / sd-picker__row / sd-picker__chosen stay in
 * the markup as identity hooks only — this comment is here so their absence
 * from this stylesheet reads as a decision, not a gap.
 *
 * Resolved (Task 7, first real mounts — partials/links.html,
 * pages/change_detail.html, pages/cab_session.html): the worry above was that
 * .bp-combobox__list stretches to the WRAPPER's width (left:0; right:0), and
 * two of those forms sat the plain ref input in a .bp-field--grow so it could
 * fill the rest of its .bp-toolbar row — harmless for a plain input, which
 * keeps its own declared .bp-input--sm width regardless of its container, but
 * exactly the mismatch this caveat predicted once a dropdown anchors to that
 * same container instead. It never needed a new CSS rule: {{template "picker"
 * .X}} replaces the WHOLE field div, wrapper included, with picker.html's own
 * — which is plain .bp-field (no --grow) — so no picker field grows past its
 * content width and no dropdown outgrows its input. Do not reintroduce
 * .bp-field--grow on a .sd-picker field without re-checking this. */

/* Sidebar collapse (desktop only). The state is an attribute the head script
   stamps on <html> before first paint, so the shell never renders wide and
   then snaps — same mechanism as data-theme. Below 720px the shell is already
   one column with the <details> disclosure, so nothing here may apply there:
   every collapsed rule sits behind min-width: 721px. The toggle exists only
   under scripting (html.sd-js); without JavaScript the sidebar is simply
   always open, which is the honest no-JS state, not a fallback control. */
.sd-nav-toggle { display: none; margin-left: auto; }
@media (min-width: 721px) {
  html.sd-js:not([data-nav="collapsed"]) .sd-nav-toggle--collapse { display: inline-flex; }
  html.sd-js[data-nav="collapsed"] .sd-nav-toggle--expand { display: inline-flex; }
  html[data-nav="collapsed"] .bp-app { grid-template-columns: 48px 1fr; }
  /* Collapsed, the rail carries exactly two things: the SD mark and the way
     back out. Everything else — nav, footer, wordmark — is hidden, not
     shrunk, so nothing half-legible remains. */
  html[data-nav="collapsed"] .sd-navdrop,
  html[data-nav="collapsed"] .bp-sidebar__brand strong { display: none; }
  html[data-nav="collapsed"] .bp-sidebar__brand {
    flex-direction: column;
    padding: var(--bp-space-2);
    gap: var(--bp-space-2);
  }
  html[data-nav="collapsed"] .sd-nav-toggle { margin-left: 0; }
}

/* Opt-in full-width page: any page that renders an .sd-page-full element lifts
   the shell's content cap for itself, reached with :has() from outside the
   shell exactly like .sd-report--print above — a one-page layout switch does
   not belong in the shell every page renders through. */
.bp-root:has(.sd-page-full) .bp-content { max-width: none; }

/* The CAB session page's two-column layout: the agenda takes the wide track,
   the session facts and controls the narrow one. minmax(0, …) so the agenda
   table may shrink instead of forcing the page to scroll sideways; one column
   again under 900px, where two tracks would starve the table. */
.sd-cab-grid {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: var(--bp-space-5);
}
@media (max-width: 900px) {
  .sd-cab-grid { grid-template-columns: minmax(0, 1fr); }
}

/* The CAB members panel: a plain list, no bullets — the roster's row shape
   without its table. */
.sd-cab-members { list-style: none; margin: 0; padding: 0; }
.sd-cab-members li + li { margin-top: var(--bp-space-2); }

/* The mirror of .sd-nojs-only above: a control that WORKS only under
   scripting (a button whose whole job is showModal) is hidden until the
   pre-paint script stamps sd-js, so a no-JS visitor never sees a button that
   does nothing. */
.sd-js-only { display: none; }
html.sd-js .sd-js-only { display: inline-block; }
/* A select restored by .sd-js must lay out as a select, not as a button. */
html.sd-js select.sd-js-only { display: inline-block; }

/* ------------------------------------------------------------------ *
 * PER-PAGE HELP                                                       *
 *   The "?" button in the top right corner and the modal it opens     *
 *   (internal/httpx/help.go, templates/partials/help.html).           *
 * ------------------------------------------------------------------ */

/* The "?" sits INLINE in the header row, between the theme control and the
   identity. It was briefly fixed to the viewport corner instead; that put it
   over the right-hand end of a page-head action row on a narrow window and
   needed a reserved gutter on two shells to stay clear. A header cell needs
   none of that, and the control is where the eye already goes for the session
   controls. Square, like every other control in this system. */
.sd-help-btn {
  width: 30px;
  height: 30px;
  padding: 0;
  display: inline-grid;
  place-items: center;
  font-weight: 700;
  font-size: var(--bp-text-base);
  line-height: 1;
  flex: none;
}

/* The desk's header row: one line above the page, controls pushed right, a
   hairline under it. The sidebar owns the top left corner with the brand, so
   this bar carries no title of its own — the page's own h1 follows it. */
.sd-deskbar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--bp-space-3);
  padding-bottom: var(--bp-space-3);
  margin-bottom: var(--bp-space-5);
  border-bottom: var(--bp-border-width) solid var(--bp-line);
}
/* The address is the one thing here that can be long; it may shrink and
   ellipsis rather than push the logout button onto a second line. */
.sd-deskbar__who {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The theme select sits in a row of .bp-btn--sm controls. .bp-select is a
   form field sized for a form, so it is brought down to the row's padding —
   there is no bp-select--sm in the system and inventing one for a single
   control would put a second sizing vocabulary in the design system. */
.sd-themeswitch {
  width: auto;
  padding: var(--bp-space-1) var(--bp-space-2);
}

/* The dialog is a CHILD of whatever header cell holds the button, and a
   <dialog> still inherits text properties from its DOM parent even while it is
   in the top layer. The public and portal shells put it inside
   .bp-topnav__user, which is `white-space: nowrap; font-size: sm` for the row
   of controls — so without this reset every line of help ran off the right
   edge behind a horizontal scrollbar. Caught in a browser; no markup assertion
   could have seen it.

   Reset rather than reparent: moving the dialog out of the header would put
   the button and the thing it opens in two different places in the document,
   which is worse for assistive technology than one inherited property. */
.sd-help {
  white-space: normal;
  font-size: var(--bp-text-base);
  text-align: left;
}

/* Definition list as label/description pairs: the label carries the control's
   own name, so it must read as the page's word for the thing, not as prose. */
.sd-help__section + .sd-help__section { margin-top: var(--bp-space-2); }
.sd-help__h {
  margin: 0 0 var(--bp-space-2);
  font-size: var(--bp-text-sm);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--bp-ink-dim);
}
.sd-help__intro { margin: 0; }
.sd-help__list { margin: 0; }
.sd-help__list dt {
  font-weight: 600;
  margin-top: var(--bp-space-3);
}
.sd-help__list dt:first-child { margin-top: 0; }
.sd-help__list dd {
  margin: 0;
  color: var(--bp-ink-dim);
}
/* A long topic must scroll INSIDE the modal, so the close button stays
   reachable on a short window. */
.sd-help .bp-modal__body { max-height: 70vh; overflow-y: auto; }
