/* ============================================================
 * JARGAN CANONICAL COMPONENT LIBRARY — BU-374
 * ------------------------------------------------------------
 * Each repeated UI concept (Card / Stat / ListRow / PageTitle /
 * Field / Section) is authored ONCE here and reused across pages.
 *
 * Rules:
 *   - Every value resolves through jargan-semantic-tokens.css.
 *     Zero hardcoded hex. Zero appearance-named classes.
 *   - Selectors are flat + class/element based.
 *     No #id styling. No !important.
 *   - Layout/arrangement driven by tokens so a theme can rearrange
 *     the migrated pages with zero markup edits.
 * ============================================================ */

/* ---- jgn-card ---- */
.jgn-card {
  background: var(--color-surface);
  border: var(--border-subtle);
  border-top: var(--border-grid);
  padding: var(--space-6);
  transition: box-shadow var(--motion-fast);
}
.jgn-card:hover {
  box-shadow: var(--shadow-hover);
  transform: var(--shadow-hover-transform);
  transition: box-shadow var(--motion-fast), transform var(--motion-fast);
}
.jgn-card__title {
  font-family: var(--font-display);
  font-size: var(--text-h3);
  font-weight: var(--weight-display);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  margin: 0 0 var(--space-2);
  color: var(--color-ink);
}
.jgn-card__body {
  color: var(--color-ink-dim);
  font-size: var(--text-body);
  line-height: var(--leading-body);
}

/* ---- jgn-stat ---- */
.jgn-stat {
  background: var(--color-surface);
  border: var(--border-subtle);
  border-top: var(--border-grid);
  padding: var(--space-5) var(--space-6);
  margin: 0;
}
.jgn-stat__value {
  display: block;
  font-family: var(--font-display);
  font-size: var(--text-display);
  font-weight: var(--weight-display);
  letter-spacing: var(--tracking-display);
  line-height: 1;
  color: var(--color-ink);
}
.jgn-stat__label {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--text-ui);
  color: var(--color-ink-muted);
}

/* ---- jgn-list-row ---- */
.jgn-list-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  background: var(--color-surface);
  border-bottom: var(--border-subtle);
  list-style: none;
  transition: background var(--motion-fast);
}
.jgn-list-row:hover {
  background: var(--color-hover-surface);
}
.jgn-list-row__main {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}
.jgn-list-row__lead {
  flex-shrink: 0;
  color: var(--color-ink-muted);
  font-size: var(--text-ui);
}
.jgn-list-row__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.jgn-list-row__title {
  font-weight: var(--weight-strong);
  color: var(--color-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.jgn-list-row__meta {
  font-size: var(--text-caption);
  color: var(--color-ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.jgn-list-row__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}
/* list container reset */
ul.jgn-list, ol.jgn-list {
  list-style: none;
  padding: 0;
  margin: 0;
  border-top: var(--border-grid);
}

/* ---- jgn-page-title ---- */
.jgn-page-title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-6);
  padding-bottom: var(--space-4);
  border-bottom: var(--border-section);
}
.jgn-page-title__heading {
  font-family: var(--font-display);
  font-size: var(--text-h1);
  font-weight: var(--weight-display);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  color: var(--color-ink);
  margin: 0;
}
.jgn-page-title__sub {
  color: var(--color-ink-muted);
  font-size: var(--text-ui);
  margin: var(--space-1) 0 0;
  flex-basis: 100%;
}
.jgn-page-title__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

/* ============================================================
 * jgn-page-header + its control bar + jgn-stage    (BU-787 / BU-795)
 * ------------------------------------------------------------
 * ONE header for the seven cockpit pages, in two variants that are the SAME
 * component at the SAME scale and differ in one slot:
 *   --library    a collection — summary line with real counts under the title.
 *   --workbench  a working surface — scope controls beside the title (Context).
 * Constant in both: kicker eyebrow, one title scale, the signature (1px baseline
 * rule + a short Traffic Orange tick at the left edge), the same padding, the
 * same left edge, the same action styling, actions right-aligned.
 *
 * WHY THIS BLOCK CARRIES ITS OWN PALETTE SLOTS
 * In the app's DEFAULT state there is no [data-theme]: <html> is painted with
 * the dark cockpit backdrop while the canonical --color-ink / --color-surface
 * ramp is still the LIGHT one, so reading --color-ink here would put near-black
 * ink on a near-black page (measured 1.15:1 on /workspaces before this card).
 * The header therefore supplies a known surface from the semantic COCKPIT ramp
 * and composites its own text against it. An explicitly chosen vibe owns canvas
 * and ink together, so the slots re-point at the vibe ramp and the header
 * repaints with the skin.
 *
 * Every value is a semantic token: zero hex literals, zero inline styles.
 * Every selector is scoped under .jgn-page-header / .jgn-stage — this file
 * loads on every route.
 * ============================================================ */

.jgn-page-header,
.jgn-page-header-bar,
.jgn-stage {
  --jgn-ph-surface:     var(--color-cockpit-surface);
  --jgn-ph-bar:         var(--color-cockpit-bg);
  --jgn-ph-field:       var(--color-cockpit-surface-2);
  --jgn-ph-hover:       var(--color-cockpit-surface-3);
  --jgn-ph-ink:         var(--color-cockpit-ink);
  --jgn-ph-ink-dim:     var(--color-cockpit-ink-dim);
  --jgn-ph-ink-muted:   var(--color-cockpit-ink-muted);
  --jgn-ph-line:        var(--color-cockpit-line);
  --jgn-ph-line-soft:   var(--color-cockpit-line-soft);
  /* Brand signal. The contrast law: white on Traffic Orange #DD5100 is 4.0:1 and
     FAILS, so every orange FILL in this system carries dark ink instead
     (--color-accent-on-dark under --color-cockpit-bg = 6.9:1). --color-accent-on-dark
     is declared once at :root and never re-mapped by a vibe, so that pair holds in
     every skin while staying unmistakably Traffic Orange. */
  --jgn-ph-accent-fill: var(--color-accent-on-dark);
  --jgn-ph-accent-ink:  var(--color-cockpit-bg);
}

/* An explicitly chosen vibe owns canvas AND ink together — follow it.
   Specificity (0,3,0) clears the (0,1,0) base above. */
[data-theme]:not([data-theme="default"]) .jgn-page-header,
[data-theme]:not([data-theme="default"]) .jgn-page-header-bar,
[data-theme]:not([data-theme="default"]) .jgn-stage {
  --jgn-ph-surface:     var(--color-surface);
  --jgn-ph-bar:         var(--color-surface-sunken);
  --jgn-ph-field:       var(--color-surface-raised);
  --jgn-ph-hover:       var(--color-hover-surface);
  --jgn-ph-ink:         var(--color-ink);
  --jgn-ph-ink-dim:     var(--color-ink-dim);
  --jgn-ph-ink-muted:   var(--color-ink-muted);
  --jgn-ph-line:        var(--color-line);
  --jgn-ph-line-soft:   var(--color-line-subtle);
}

/* ------------------------------------------------------------
 * The control-bar family (BU-801) — JgnControlBar and the five shared
 * filter controls it hosts (JgnSearchField, JgnVoiceFilter, JgnChannelFilter,
 * JgnSortControl, JgnViewToggle). Same palette contract as above, kept in a
 * SEPARATE rule so this unit never rewrites the .jgn-page-header / .jgn-stage
 * selector text that the token gate already cleared. Every selector below is
 * scoped under .jgn-control-bar, .jgn-search, .jgn-filter, .jgn-sort or
 * .jgn-viewtoggle — this file loads on every route, so a loose selector would
 * restyle the whole app.
 * ------------------------------------------------------------ */
.jgn-control-bar,
.jgn-search,
.jgn-filter,
.jgn-sort,
.jgn-viewtoggle {
  --jgn-ph-surface:     var(--color-cockpit-surface);
  --jgn-ph-bar:         var(--color-cockpit-bg);
  --jgn-ph-field:       var(--color-cockpit-surface-2);
  --jgn-ph-hover:       var(--color-cockpit-surface-3);
  --jgn-ph-ink:         var(--color-cockpit-ink);
  --jgn-ph-ink-dim:     var(--color-cockpit-ink-dim);
  --jgn-ph-ink-muted:   var(--color-cockpit-ink-muted);
  --jgn-ph-line:        var(--color-cockpit-line);
  --jgn-ph-line-soft:   var(--color-cockpit-line-soft);
  --jgn-ph-accent-fill: var(--color-accent-on-dark);
  --jgn-ph-accent-ink:  var(--color-cockpit-bg);
}
[data-theme]:not([data-theme="default"]) .jgn-control-bar,
[data-theme]:not([data-theme="default"]) .jgn-search,
[data-theme]:not([data-theme="default"]) .jgn-filter,
[data-theme]:not([data-theme="default"]) .jgn-sort,
[data-theme]:not([data-theme="default"]) .jgn-viewtoggle {
  --jgn-ph-surface:     var(--color-surface);
  --jgn-ph-bar:         var(--color-surface-sunken);
  --jgn-ph-field:       var(--color-surface-raised);
  --jgn-ph-hover:       var(--color-hover-surface);
  --jgn-ph-ink:         var(--color-ink);
  --jgn-ph-ink-dim:     var(--color-ink-dim);
  --jgn-ph-ink-muted:   var(--color-ink-muted);
  --jgn-ph-line:        var(--color-line);
  --jgn-ph-line-soft:   var(--color-line-subtle);
}

/* ---- the header ---- */
.jgn-page-header {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-5) var(--space-6);
  background: var(--jgn-ph-surface);
  color: var(--jgn-ph-ink);
  /* the signature, half 1: a 1px baseline rule */
  border-bottom: 1px solid var(--jgn-ph-line);
}
/* the signature, half 2: a short Traffic Orange tick at the left edge, sitting on
   the baseline rule. With the one primary action, the only accent on the header. */
.jgn-page-header::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -1px;
  width: var(--space-10);
  height: 2px;
  background: var(--jgn-ph-accent-fill);
}

.jgn-page-header__identity {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* Eyebrow. Hierarchy is carried by size + weight + tracking, never by colour —
   the accent is a signal, not a heading tint. */
.jgn-page-header__kicker {
  margin: 0 0 var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-eyebrow);
  line-height: var(--leading-ui);
  text-transform: uppercase;
  color: var(--jgn-ph-ink-dim);
}
.jgn-page-header__headline {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-3);
  min-width: 0;
}
/* ONE title scale across all seven pages — no display-size titles anywhere. */
.jgn-page-header__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-h2);
  font-weight: var(--weight-display);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  color: var(--jgn-ph-ink);
}
.jgn-page-header__meta,
.jgn-page-header__sub {
  min-width: 0;
  font-size: var(--text-ui);
  line-height: var(--leading-ui);
  color: var(--jgn-ph-ink-dim);
}
.jgn-page-header__sub {
  margin: var(--space-2) 0 0;
}
.jgn-page-header__context {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  min-width: 0;
  flex: 1 1 auto;
}
.jgn-page-header__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  flex-shrink: 0;
  margin-left: auto;
}

/* ---- library: identity-first, the summary line carries the counts ---- */
.jgn-page-header--library .jgn-page-header__actions {
  /* align the collection-level actions with the title, not with the eyebrow */
  padding-top: var(--space-4);
}

/* ---- workbench: one compact row, scope first ---- */
.jgn-page-header--workbench {
  align-items: center;
  padding-top: var(--space-4);
  padding-bottom: var(--space-4);
}
.jgn-page-header--workbench .jgn-page-header__identity {
  flex-shrink: 0;
}
.jgn-page-header--workbench .jgn-page-header__kicker {
  margin-bottom: var(--space-1);
}
.jgn-page-header--workbench .jgn-page-header__sub {
  margin-top: var(--space-1);
}

/* ---- actions: opt-in control styling ----
 * Opt-in by class, never by descendant selector: adopting pages move their own
 * buttons into the Actions slot and must keep their behaviour and their look
 * unless they ask for these. */
.jgn-page-header__action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--jgn-ph-line);
  border-radius: var(--radius-sm);
  background: var(--jgn-ph-field);
  color: var(--jgn-ph-ink);
  font-family: inherit;
  font-size: var(--text-ui);
  font-weight: var(--weight-medium);
  line-height: var(--leading-ui);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion-fast), border-color var(--motion-fast);
}
.jgn-page-header__action:hover {
  background: var(--jgn-ph-hover);
  color: var(--jgn-ph-ink);
  text-decoration: none;
}
.jgn-page-header__action:focus-visible {
  outline: 2px solid var(--jgn-ph-accent-fill);
  outline-offset: 2px;
}
.jgn-page-header__action > svg {
  flex-shrink: 0;
}
/* exactly one of these per page */
.jgn-page-header__action--primary,
.jgn-page-header__action--primary:hover {
  border-color: transparent;
  background: var(--jgn-ph-accent-fill);
  color: var(--jgn-ph-accent-ink);
  font-weight: var(--weight-strong);
}
.jgn-page-header__action--icon {
  padding: var(--space-2);
  color: var(--jgn-ph-ink-dim);
}
.jgn-page-header__action--on {
  border-color: var(--jgn-ph-accent-fill);
  color: var(--jgn-ph-ink);
}
.jgn-page-header__action:disabled {
  color: var(--jgn-ph-ink-muted);
  cursor: not-allowed;
}
/* a hairline that separates one action cluster from the next */
.jgn-page-header__action-divider {
  width: 1px;
  align-self: stretch;
  margin: 0 var(--space-1);
  background: var(--jgn-ph-line);
}

/* ---- BU-823: ABOVE THE FOLD ----
 * Beau, 2026-08-23, verbatim: every LIBRARY page must show its first library
 * objects above the fold -- all chrome except the library section itself
 * capped at the top 15% of the viewport with the shared control bar
 * collapsed, at 390x844 and 768x1024. 15% of those two heights is ~127px and
 * ~154px; a library header at its normal padding and line spacing plus even a
 * COLLAPSED control bar summary comfortably clears that on its own; a
 * subtitle/summary line under the title does not (it is real content, not
 * chrome, but it still counts against the budget the card sets, and the card
 * calls it "an optional qualifier" for the library variant -- see
 * JgnPageHeader.razor). It is dropped here, at the small-viewport widths this
 * job already uses (SMALL_VIEWPORT_MAX = 900px, matching density-gate.mjs),
 * and ONLY on the library variant -- the workbench variant carries no
 * subtitle line in this budget's calculation and is unaffected.
 *
 * Padding and type already shrink at this width via the small-viewport token
 * overrides in jargan-semantic-tokens.css (--space-5/--space-6, --text-h2,
 * etc.) -- this block is the REMAINDER: the one thing tokens alone cannot do,
 * which is drop a whole line of content, plus the tightened margins that make
 * the header a single compact block instead of three loosely-spaced lines. */
@media (max-width: 900px) {
  /* Specificity note: jgn-components.css:1690's shape-based `main
     :is(header,section,article)[class*="-head"] { padding: 12px 14px
     !important }` matches this element too (a class token containing
     "-header" also contains "-head"), and wins ties on TYPE count even
     though it comes later in the cascade. Doubling the class here
     (.jgn-page-header.jgn-page-header--library) raises the CLASS count
     instead, which the specificity algorithm compares before type count, so
     this !important rule -- also !important, so still comparing on
     specificity, not source order -- wins outright rather than by luck of
     placement. */
  .jgn-page-header.jgn-page-header--library {
    padding-top: var(--space-2) !important;
    padding-bottom: var(--space-2) !important;
  }
  .jgn-page-header--library .jgn-page-header__kicker {
    margin-bottom: var(--space-1);
  }
  .jgn-page-header--library .jgn-page-header__sub {
    display: none;
  }
  .jgn-page-header--library .jgn-page-header__actions {
    padding-top: 0;
  }

  /* THE WRAP WAS THE REAL COST, NOT THE PADDING. Measured on /plans at 390:
     .jgn-page-header__identity 209.8px + .jgn-page-header__actions 208.8px
     inside a 346px content box -- the two never fit on one line, so
     justify-content:space-between (inherited from .jgn-page-header) wrapped
     Actions onto a second row and that second row, not the padding, was most
     of the header's height (111px measured; identity alone is 41.8px).
     Icon-only actions is the lever: PlanLibraryPage's two actions ("All
     plans" reload, "New plan" primary) already carry an <Icon> plus their own
     label text, so font-size:0 removes only the label's rendered box (the
     icon's width/height are SVG attributes, not em-relative, so it is
     unaffected) while the label stays in the accessibility tree and each
     anchor keeps its `title` tooltip. Two icon buttons comfortably fit beside
     the identity block on one row, which is what keeps the header a single
     compact band instead of two. */
  .jgn-page-header--library .jgn-page-header__action {
    font-size: 0;
    gap: 0;
    padding: var(--space-2);
  }
  .jgn-page-header--library .jgn-page-header__actions {
    gap: var(--space-2);
  }
}

/* The hand-built control bar (.jgn-page-header-bar and its __control shape) was
 * retired by BU-801. Seven pages looking alike while each still wrote its own
 * <select> was the same defect rev 1 of the header had: resemblance instead of
 * reuse. Its rules now live in the shared controls themselves — see the
 * "SHARED CONTROL-BAR SET" block below (.jgn-control-bar, .jgn-search,
 * .jgn-filter, .jgn-sort, .jgn-viewtoggle). */

/* ============================================================
 * the SHARED CONTROL-BAR SET    (BU-801)
 * ------------------------------------------------------------
 * Beau, 2026-08-15: "When you're searching for media objects that have a certain
 * voice or assets that have a certain voice, you can easily just reuse that
 * layout and component."
 *
 * .jgn-control-bar is the band; its contents are SHARED COMPONENTS, one per
 * thing being filtered — .jgn-search, .jgn-filter--voice, .jgn-stage,
 * .jgn-filter--channel, .jgn-sort, .jgn-viewtoggle. This block is what makes
 * them read as one grammar: a mono uppercase micro-label over every control, one
 * shape and one border on every input, select, chip and segment, and Traffic
 * Orange on ACTIVE FILTER STATE and nothing else.
 *
 * PALETTE. These roots take the same --jgn-ph-* slots the header block declares
 * at the top of this section, for the same reason: in the app's default state
 * <html> carries the dark cockpit backdrop while the canonical --color-ink ramp
 * is still the LIGHT one, so reading --color-ink here would put near-black ink on
 * a near-black page. An explicitly chosen vibe owns canvas and ink together, so
 * the slots re-point at the vibe ramp and these controls repaint with the skin.
 *
 * INK. Every readable surface in this block is --jgn-ph-ink or --jgn-ph-ink-dim,
 * never --jgn-ph-ink-muted. Measured, on the three grounds these controls sit on:
 *   ink-dim on the band     default rgb(199,208,220) on rgb(11,13,17)  = 12.49:1
 *                           dark    rgb(168,176,192) on rgb(15,52,96)  =  5.73:1
 *                           sepia   rgb(94,80,56)    on rgb(232,221,200) = 5.82:1
 *   ink-dim on a control    default 10.52:1 · dark 6.51:1 · sepia 7.53:1
 *   accent fill + its ink   rgb(11,13,17) on rgb(255,107,26)          =  6.82:1
 * ink-muted would have landed at 4.06:1 under the dark vibe — the exact sub-AA
 * escape the token gate exists to catch, so it is not used here.
 *
 * SCOPE. This stylesheet loads on EVERY route, so every selector below is under
 * .jgn-control-bar, .jgn-search, .jgn-filter, .jgn-sort or .jgn-viewtoggle. The
 * controls also have to look right OUTSIDE the band (the /design gallery mounts
 * each one on its own, and a page may put a view toggle in the header's Context
 * slot), which is why the label and control rules hang off the control roots
 * rather than off the band.
 * ============================================================ */

/* ---- the band: one darker strip on the header's left edge ---- */
.jgn-control-bar {
  position: relative;
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--space-5);
  padding: var(--space-3) var(--space-6);
  background: var(--jgn-ph-bar);
  border-bottom: 1px solid var(--jgn-ph-line);
  color: var(--jgn-ph-ink);
}

/* ---- BU-823: the collapsed resting state ----
 * Desktop keeps the body open. Small viewports start with the checkbox
 * unchecked, so only the summary row is visible until the user opens it. */
.jgn-control-bar__summary {
  order: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  align-self: flex-start;
  min-height: 32px;
  width: 32px;
  padding: 0 var(--space-2);
  border: 1px solid var(--jgn-ph-line);
  border-radius: var(--radius-sm);
  background: var(--jgn-ph-field);
  font: inherit;
  cursor: pointer;
  list-style: none;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--jgn-ph-ink-dim);
  -webkit-tap-highlight-color: transparent;
}
.jgn-control-bar__summary span {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
/* Chrome/Safari/Firefox all add a default disclosure triangle marker; the
   chevron icon in the markup is the one glyph, so the native marker is
   suppressed in both the WebKit/Blink and the Firefox spelling. */
.jgn-control-bar__summary > svg {
  flex-shrink: 0;
  transition: transform var(--motion-fast);
}
.jgn-control-bar--expanded .jgn-control-bar__summary > svg {
  transform: rotate(180deg);
}
.jgn-control-bar__summary:focus-visible {
  outline: 2px solid var(--jgn-ph-accent-fill);
  outline-offset: 2px;
}
.jgn-control-bar__collapsed-content {
  display: none;
  min-width: 0;
}
.jgn-control-bar__utility {
  display: none;
  min-width: 0;
}
.jgn-control-bar__body {
  order: 1;
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--space-5);
  flex: 1 1 0;
  min-width: 0;
}
.jgn-control-bar--collapsed .jgn-control-bar__body {
  display: none;
}

/* A control a page genuinely owns alone (Media's Assignment, Templates' Domain,
   Compose's Columns) uses this group instead of a shared component — same label,
   same shape, so the band reads the same either way. */
.jgn-control-bar__group {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}
.jgn-control-bar__row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  min-width: 0;
}

/* THE MICRO-LABEL. Every control in the set carries one; it names the thing being
   filtered and nothing else. Hierarchy is size + weight + tracking, never colour. */
.jgn-control-bar__label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  line-height: var(--leading-ui);
  color: var(--jgn-ph-ink-dim);
}
.jgn-control-bar__label > svg {
  flex-shrink: 0;
}

/* The one shape, for a page-owned control that has no shared component yet. */
.jgn-control-bar__control {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  max-width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--jgn-ph-line);
  border-radius: var(--radius-sm);
  background: var(--jgn-ph-field);
  color: var(--jgn-ph-ink);
  font-family: inherit;
  font-size: var(--text-ui);
  line-height: var(--leading-ui);
  cursor: pointer;
}
select.jgn-control-bar__control,
.jgn-sort__field {
  -webkit-appearance: none;
  appearance: none;
  min-width: 112px;
  padding-right: 32px;
  background-color: var(--jgn-ph-field);
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position:
    calc(100% - 16px) 50%,
    calc(100% - 11px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
select.jgn-control-bar__control option,
.jgn-sort__field option {
  background: var(--jgn-ph-surface);
  color: var(--jgn-ph-ink);
}
.jgn-control-bar__control:focus-visible {
  outline: 2px solid var(--jgn-ph-accent-fill);
  outline-offset: 1px;
}
.jgn-control-bar__control--on {
  border-color: transparent;
  background: var(--jgn-ph-accent-fill);
  color: var(--jgn-ph-accent-ink);
  font-weight: var(--weight-strong);
}
.jgn-control-bar__control--quiet {
  background: transparent;
  color: var(--jgn-ph-ink-dim);
}
.jgn-control-bar__control > svg {
  flex-shrink: 0;
}
/* a plain-language note where a page has no control of this kind today */
.jgn-control-bar__note {
  margin: 0;
  font-size: var(--text-ui);
  line-height: var(--leading-ui);
  color: var(--jgn-ph-ink-dim);
}
.jgn-control-bar__spacer {
  flex: 1 1 auto;
}

/* ============================================================
 * jgn-library-frame — shared spine for infinite-scroll libraries
 * Header -> collapsed controls/insights -> library objects. Pages may pass
 * their own library-panel classes while retaining this ordering contract.
 * ============================================================ */
.jgn-library-frame {
  display: flex;
  flex-direction: column;
  min-height: 0;
  width: 100%;
  color: var(--jgn-ph-ink);
}

.jgn-library-frame__control-surface {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  width: 100%;
  min-width: 0;
}

.jgn-library-frame__controlbar {
  box-shadow: none;
}

.jgn-library-frame__controlbar--sticky {
  position: sticky;
  top: var(--jgn-library-frame-sticky-top, 52px);
  z-index: var(--z-sticky, 950);
}

body:has(.vlb-cockpit) .app-shell__content {
  padding: 3px 0 0;
}

.vlb-controlbar {
  --jgn-ph-bar: var(--panel);
  --jgn-ph-field: var(--panel-2);
  --jgn-ph-hover: var(--panel-hover);
  --jgn-ph-ink: var(--text);
  --jgn-ph-ink-dim: var(--text-2);
  --jgn-ph-ink-muted: var(--text-3);
  --jgn-ph-line: var(--line);
  --jgn-ph-line-soft: var(--line-soft);
  background: var(--panel);
  padding-top: 0;
  padding-bottom: 0;
  border: 1px solid var(--line);
  border-radius: var(--vl-r-container);
  backdrop-filter: var(--vl-blur);
  -webkit-backdrop-filter: var(--vl-blur);
  margin-bottom: 6px;
  min-height: 44px;
}

.vlb-controlbar.jgn-library-frame__controlbar--sticky {
  top: 0;
}

.vlb-controlbar .jgn-control-bar__summary {
  position: absolute;
  top: 5px;
  right: 6px;
  z-index: 4;
  height: 32px;
  width: 32px;
  border-left: 0;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.vlb-controlbar .jgn-control-bar__utility {
  position: absolute;
  top: 5px;
  right: 38px;
  z-index: 3;
  display: flex;
  align-items: center;
  min-width: 0;
}

.vlb-controlbar .vlb-utilitybar {
  position: static;
  z-index: 3;
}

.vlb-controlbar .jgn-control-bar__summary span {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.vlb-controlbar .jgn-control-bar__collapsed-content {
  position: absolute;
  top: 5px;
  right: 140px;
  left: 6px;
  z-index: 1;
  height: 32px;
  align-items: center;
  min-width: 0;
}

.vlb-compact-search {
  width: 100%;
  min-width: 0;
}

.vlb-compact-search .jgn-control-bar__label {
  display: none;
}

.vlb-compact-search .jgn-search__box {
  width: 100%;
  height: 32px;
  min-height: 32px;
  padding: 0 10px;
}

.vlb-compact-search .jgn-search__input {
  width: 100%;
}

.vlb-controlbar .jgn-control-bar__body {
  padding-top: 5px;
  padding-right: 0;
}

.vlb-controlbar .jgn-library-frame__controls {
  box-sizing: border-box;
  padding-right: 148px;
  gap: 10px;
}

.vlb-controlbar .jgn-search__input {
  width: 190px;
}

.vlb-controlbar .vlb-dd__trigger {
  min-width: 108px;
  max-width: 150px;
}

.vlb-controlbar .jgn-control-bar__label {
  line-height: 1;
}

.vlb-controlbar.jgn-control-bar--collapsed .jgn-control-bar__collapsed-content {
  display: flex;
}

.jgn-library-frame__controls,
.jgn-library-frame__insights {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--space-4);
  min-width: 0;
  width: 100%;
}

.jgn-library-frame__insights {
  align-items: stretch;
}

.jgn-library-frame__library {
  min-width: 0;
}

.jgn-library-frame__library-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-width: 0;
}

.jgn-library-frame__library-body {
  min-width: 0;
}

@media (max-width: 900px) {
  .jgn-library-frame__controls,
  .jgn-library-frame__insights {
    align-items: stretch;
    gap: var(--space-3);
  }
}

/* ---- shared shell: label stacked over the control, for every member ---- */
.jgn-search,
.jgn-filter,
.jgn-sort,
.jgn-viewtoggle {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
  color: var(--jgn-ph-ink);
}

/* ---- JgnSearchField ---- */
.jgn-search__box {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--jgn-ph-line);
  border-radius: var(--radius-sm);
  background: var(--jgn-ph-field);
  color: var(--jgn-ph-ink);
}
.jgn-search__input {
  min-width: 0;
  width: 240px;
  max-width: 100%;
  border: none;
  background: transparent;
  color: var(--jgn-ph-ink);
  font-family: inherit;
  font-size: var(--text-ui);
  line-height: var(--leading-ui);
}
.jgn-search__input:focus {
  outline: none;
}
.jgn-search__input::placeholder {
  color: var(--jgn-ph-ink-dim);
}
/* Safari paints its own clear affordance on type=search; this control has one. */
.jgn-search__input::-webkit-search-decoration,
.jgn-search__input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}
.jgn-search__box:focus-within {
  outline: 2px solid var(--jgn-ph-accent-fill);
  outline-offset: 1px;
}
/* ORANGE = ACTIVE FILTER STATE. A search box only earns the accent while it
   actually carries text; an empty box is not filtering anything. */
.jgn-search.is-active .jgn-search__box {
  border-color: var(--jgn-ph-accent-fill);
}
.jgn-search__clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--jgn-ph-ink-dim);
  cursor: pointer;
}
.jgn-search__clear:hover {
  color: var(--jgn-ph-ink);
}
.jgn-search__clear:focus-visible {
  outline: 2px solid var(--jgn-ph-accent-fill);
  outline-offset: 2px;
}
.jgn-search--readonly .jgn-search__input,
.jgn-search--readonly .jgn-search__clear {
  cursor: default;
}

/* ---- JgnVoiceFilter + JgnChannelFilter: one shape, two things filtered ----
   The two components are deliberately styled by the same rules. A page that has
   learned to read the voice filter can read the channel filter without learning
   anything; what differs is the label, the icon and the data. */
.jgn-filter__opts {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-1);
  min-width: 0;
}
.jgn-filter__opt {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  max-width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--jgn-ph-line);
  border-radius: var(--radius-sm);
  background: var(--jgn-ph-field);
  color: var(--jgn-ph-ink-dim);
  font-family: inherit;
  font-size: var(--text-ui);
  line-height: var(--leading-ui);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--motion-fast), border-color var(--motion-fast);
}
.jgn-filter__opt:hover {
  background: var(--jgn-ph-hover);
  color: var(--jgn-ph-ink);
}
.jgn-filter__opt:focus-visible {
  outline: 2px solid var(--jgn-ph-accent-fill);
  outline-offset: 1px;
}
/* ORANGE = this voice / this channel is actively filtering. Same treatment an
   included stage takes in .jgn-stage, so the three filters read as one family. */
.jgn-filter__opt.is-on,
.jgn-filter__opt.is-on:hover {
  border-color: transparent;
  background: var(--jgn-ph-accent-fill);
  color: var(--jgn-ph-accent-ink);
  font-weight: var(--weight-strong);
}
/* The "not filtering" option is the resting state, not a selection: when it is
   the active one the control is doing nothing, so it stays neutral and simply
   reads as pressed. */
.jgn-filter__opt--all.is-on,
.jgn-filter__opt--all.is-on:hover {
  border-color: var(--jgn-ph-line);
  background: var(--jgn-ph-hover);
  color: var(--jgn-ph-ink);
  font-weight: var(--weight-medium);
}
.jgn-filter__opt--more {
  background: transparent;
  color: var(--jgn-ph-ink-dim);
}
.jgn-filter--readonly .jgn-filter__opt {
  cursor: default;
}
.jgn-filter--readonly .jgn-filter__opt:hover {
  background: var(--jgn-ph-field);
  color: var(--jgn-ph-ink-dim);
}
.jgn-filter--readonly .jgn-filter__opt.is-on:hover {
  background: var(--jgn-ph-accent-fill);
  color: var(--jgn-ph-accent-ink);
}

/* ---- JgnSortControl ---- */
.jgn-sort__row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}
.jgn-sort__field,
.jgn-sort__dir {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  max-width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--jgn-ph-line);
  border-radius: var(--radius-sm);
  background: var(--jgn-ph-field);
  color: var(--jgn-ph-ink);
  font-family: inherit;
  font-size: var(--text-ui);
  line-height: var(--leading-ui);
  cursor: pointer;
}
.jgn-sort__field:focus-visible,
.jgn-sort__dir:focus-visible {
  outline: 2px solid var(--jgn-ph-accent-fill);
  outline-offset: 1px;
}
.jgn-sort__dir:hover {
  background: var(--jgn-ph-hover);
}
.jgn-sort__dir > svg {
  flex-shrink: 0;
}
.jgn-sort__dir-text {
  white-space: nowrap;
}
.jgn-sort--readonly .jgn-sort__field,
.jgn-sort--readonly .jgn-sort__dir {
  cursor: default;
}

/* ---- JgnViewToggle: the same shape, welded into one run ---- */
.jgn-viewtoggle__seg {
  display: inline-flex;
  align-items: stretch;
  flex-wrap: wrap;
  max-width: 100%;
  border: 1px solid var(--jgn-ph-line);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.jgn-viewtoggle__opt {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: none;
  background: transparent;
  color: var(--jgn-ph-ink-dim);
  font-family: inherit;
  font-size: var(--text-ui);
  line-height: var(--leading-ui);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--motion-fast);
}
.jgn-viewtoggle__opt + .jgn-viewtoggle__opt {
  border-left: 1px solid var(--jgn-ph-line);
}
.jgn-viewtoggle__opt:hover {
  background: var(--jgn-ph-hover);
  color: var(--jgn-ph-ink);
}
.jgn-viewtoggle__opt:focus-visible {
  outline: 2px solid var(--jgn-ph-accent-fill);
  outline-offset: -2px;
}
/* The view you are looking at, in the accent — the state that changes what is on
   the page, the same reason an included stage is orange. */
.jgn-viewtoggle__opt.is-on,
.jgn-viewtoggle__opt.is-on:hover {
  background: var(--jgn-ph-accent-fill);
  color: var(--jgn-ph-accent-ink);
  font-weight: var(--weight-strong);
}
.jgn-viewtoggle__opt > svg {
  flex-shrink: 0;
}
.jgn-viewtoggle--readonly .jgn-viewtoggle__opt {
  cursor: default;
}

.vlb-viewtoggle--icons .jgn-viewtoggle__seg {
  height: 34px;
  flex-wrap: nowrap;
}

.vlb-viewtoggle--icons .jgn-viewtoggle__opt {
  justify-content: center;
  width: 34px;
  min-width: 34px;
  padding: 0;
}

.vlb-viewtoggle--icons .jgn-viewtoggle__text {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ---- the band at narrow widths ----
   Controls wrap rather than shrink: a half-legible search box and a clipped chip
   row are both worse than a second line. */
@media (max-width: 900px) {
  .jgn-control-bar {
    gap: var(--space-4);
    padding-left: var(--space-4);
    padding-right: var(--space-4);
  }
  .jgn-search__input {
    width: 100%;
  }

  /* ---- BU-823: COLLAPSE THE BAND ----
     Beau, 2026-08-23: every library page shows its first library objects above
     the fold at 390x844 / 768x1024, all chrome capped at 15% of viewport
     height, WITH THE CONTROL BAR COLLAPSED. Below SMALL_VIEWPORT_MAX the
     summary toggle appears and untouched compact viewports default to the
     collapsed body, so a fresh page load shows one ~36px summary row, not the
     full filter set. */
  .jgn-control-bar__summary {
    order: 2;
    display: flex;
    flex: 0 0 auto;
    justify-content: center;
    min-height: 32px;   /* comfortably tappable; the 36px house number is a
                            CEILING per density-gate.mjs, not a floor, so a
                            collapsed summary -- which exists purely to save
                            vertical space -- is free to sit under it */
  }
  .jgn-control-bar__summary span {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip: auto;
    white-space: normal;
  }
  .jgn-control-bar--expanded .jgn-control-bar__summary,
  .jgn-control-bar--collapsed .jgn-control-bar__summary {
    flex: 0 0 auto;
    width: 32px;
  }
  .jgn-control-bar:not(.jgn-control-bar--expanded) .jgn-control-bar__collapsed-content {
    display: flex;
  }
  .jgn-control-bar--expanded .jgn-control-bar__collapsed-content {
    display: none;
  }
  /* The collapsed band is chrome, not a filter row: its OWN vertical padding
     is part of the 15% above-the-fold budget, so it goes to the tightest
     rhythm step rather than the control-row step used when the band is open
     and doing real filtering work. */
  .jgn-control-bar {
    padding-top: var(--space-2);
    padding-bottom: var(--space-2);
  }
  .jgn-control-bar:not(.jgn-control-bar--expanded) .jgn-control-bar__body {
    display: none;
  }
  .jgn-control-bar--expanded .jgn-control-bar__body {
    display: flex;
  }
}

@media (max-width: 1320px) {
  .jgn-library-frame__controlbar {
    gap: var(--space-4);
    padding-top: var(--space-2);
    padding-right: var(--space-4);
    padding-bottom: var(--space-2);
    padding-left: var(--space-4);
  }

  .jgn-library-frame__controlbar .jgn-search__input {
    width: 100%;
  }

  .jgn-library-frame__controlbar .jgn-library-frame__controls {
    padding-right: 0;
  }

  .jgn-library-frame__controlbar .jgn-control-bar__summary {
    order: 2;
    display: flex;
    flex: 0 0 auto;
    justify-content: center;
    min-height: 32px;
  }

  .jgn-library-frame__controlbar.jgn-control-bar--expanded .jgn-control-bar__summary,
  .jgn-library-frame__controlbar.jgn-control-bar--collapsed .jgn-control-bar__summary {
    flex: 0 0 auto;
    width: 32px;
  }

  .jgn-library-frame__controlbar:not(.jgn-control-bar--expanded) .jgn-control-bar__collapsed-content {
    display: flex;
  }

  .jgn-library-frame__controlbar.jgn-control-bar--expanded .jgn-control-bar__collapsed-content {
    display: none;
  }

  .jgn-library-frame__controlbar:not(.jgn-control-bar--expanded) .jgn-control-bar__body {
    display: none;
  }

  .jgn-library-frame__controlbar.jgn-control-bar--expanded .jgn-control-bar__body {
    display: flex;
  }
}

/* ============================================================
 * jgn-stage — the one stage control (BU-795)
 * Traffic Orange = INCLUDED. Excluded is struck-through and dimmed, never a
 * second colour, never green. Off is the neutral resting shape.
 * ============================================================ */
.jgn-stage {
  display: inline-flex;
  align-items: stretch;
  flex-wrap: wrap;
  gap: var(--space-1);
}
.jgn-stage__opt {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-width: 32px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--jgn-ph-line);
  border-radius: var(--radius-sm);
  background: var(--jgn-ph-field);
  color: var(--jgn-ph-ink-dim);
  font-family: inherit;
  font-size: var(--text-ui);
  line-height: var(--leading-ui);
  cursor: pointer;
  transition: background var(--motion-fast), border-color var(--motion-fast);
}
.jgn-stage__opt:hover {
  background: var(--jgn-ph-hover);
  color: var(--jgn-ph-ink);
}
.jgn-stage__opt:focus-visible {
  outline: 2px solid var(--jgn-ph-accent-fill);
  outline-offset: 1px;
}
.jgn-stage__letter {
  font-family: var(--font-mono);
  font-size: var(--text-mono);
  font-weight: var(--weight-strong);
  line-height: 1;
}
.jgn-stage__name {
  font-size: var(--text-ui);
  white-space: nowrap;
}
/* included */
.jgn-stage__opt.is-included,
.jgn-stage__opt.is-included:hover {
  border-color: transparent;
  background: var(--jgn-ph-accent-fill);
  color: var(--jgn-ph-accent-ink);
  font-weight: var(--weight-strong);
}
/* excluded — struck through and dimmed, no second colour */
.jgn-stage__opt.is-excluded,
.jgn-stage__opt.is-excluded:hover {
  border-style: dashed;
  border-color: var(--jgn-ph-line);
  background: transparent;
  color: var(--jgn-ph-ink-muted);
  text-decoration: line-through;
  text-decoration-thickness: 2px;
}
/* read-only: the same states, no interaction */
.jgn-stage--readonly .jgn-stage__opt,
.jgn-stage__opt:disabled {
  cursor: default;
}
.jgn-stage--readonly .jgn-stage__opt:hover {
  background: var(--jgn-ph-field);
  color: var(--jgn-ph-ink-dim);
}
.jgn-stage--readonly .jgn-stage__opt.is-included:hover {
  background: var(--jgn-ph-accent-fill);
  color: var(--jgn-ph-accent-ink);
}
.jgn-stage--narrow .jgn-stage__opt {
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}

/* ---- jgn-field ---- */
.jgn-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.jgn-field > label {
  font-size: var(--text-ui);
  font-weight: var(--weight-strong);
  color: var(--color-ink);
}
.jgn-field > input,
.jgn-field > select,
.jgn-field > textarea {
  font: inherit;
  font-size: var(--text-body);
  padding: var(--space-3) var(--space-3);
  color: var(--color-ink);
  background: var(--color-surface);
  border: var(--border-subtle);
  border-radius: var(--radius-sm);
  transition: border-color var(--motion-fast);
}
.jgn-field > input:focus,
.jgn-field > select:focus,
.jgn-field > textarea:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
  border-color: var(--color-accent);
}
.jgn-field__help {
  font-size: var(--text-caption);
  color: var(--color-ink-muted);
}

/* ---- jgn-section ---- */
.jgn-section {
  padding: var(--space-8) 0;
  border-top: var(--border-subtle);
}
.jgn-section__heading {
  font-family: var(--font-display);
  font-size: var(--text-h2);
  font-weight: var(--weight-display);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  color: var(--color-ink);
  margin: 0 0 var(--space-4);
}
.jgn-section__body {
  color: var(--color-ink-dim);
}
.jgn-section--flush {
  border-top: none;
  padding-top: 0;
}

/* ---- Stat grid (auto-fit, themeable via --grid-min) ---- */
.jgn-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--grid-min, 180px), 1fr));
  gap: 1px;
  background: var(--color-line);
  border: var(--border-grid);
}
.jgn-stat-grid > .jgn-stat {
  border: none;
}

/* ---- Card grid ---- */
.jgn-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--grid-min, 220px), 1fr));
  gap: var(--space-4);
}

/* ---- Detail table (jgn-detail) ---- */
.jgn-detail {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-body);
}
.jgn-detail th,
.jgn-detail td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  border-bottom: var(--border-subtle);
  vertical-align: top;
}
.jgn-detail th {
  font-weight: var(--weight-strong);
  color: var(--color-ink-muted);
  font-size: var(--text-caption);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  white-space: nowrap;
  width: 30%;
}
.jgn-detail td {
  color: var(--color-ink);
}

/* ---- Partner/entity badge (inline semantic label) ---- */
.jgn-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-caption);
  font-weight: var(--weight-strong);
  padding: 2px var(--space-2);
  border: var(--border-subtle);
  border-radius: var(--radius-sm);
  color: var(--color-ink-dim);
  background: var(--color-hover-surface);
}
.jgn-badge--accent {
  background: var(--color-accent-tint);
  color: var(--color-accent-aa);
  border-color: var(--color-accent-tint);
}

/* ============================================================
 * BOOTSTRAP PRIMITIVE → TOKEN BRIDGE (PM-010 Phase 3)
 * This Bootstrap build hardcodes `background-color:var(--color-surface)` as a LITERAL on
 * .card / .form-control / .form-select (not via --bs-card-bg), so a variant
 * can't recolor them by remapping --bs-*. Route them through --color-surface
 * instead. Loaded after bootstrap.min.css, so it wins at equal specificity;
 * app components with higher specificity still win. Default --color-surface
 * is #ffffff, so the un-skinned app is pixel-identical; CRT/Frutiger recolor
 * via the token. No !important — this is tokenization, not a floor.
 * ============================================================ */
.card { background-color: var(--color-surface); }
.form-control,
.form-select { background-color: var(--color-surface); }

/* TEXT + BORDER bridge: this Bootstrap build also hardcodes `color:var(--color-ink)` and
 * `border-color:var(--color-line)/#ced4da` as LITERALS on tables/forms/buttons/dropdowns
 * (not via --bs-* vars), so table/form text stays dark on a themed-dark surface
 * (light text on a themed surface is the inverse failure). Route them through the
 * canonical ink/line tokens so they flip with the theme. Default --color-ink
 * (#1a1a1a) vs Bootstrap #212529 is a sub-perceptible neutral shift (passes the
 * default-identity gate); themes flip them light. No !important — tokenization. */
.table,
.form-control, .form-control:focus, .form-control-plaintext,
.form-select, .input-group-text, kbd,
.dropdown-menu, .dropdown-item, .list-group-item { color: var(--color-ink); }
/* Bootstrap caches table state-text as literal #212529 vars too */
.table { --bs-table-color: var(--color-ink); --bs-table-striped-color: var(--color-ink); --bs-table-active-color: var(--color-ink); --bs-table-hover-color: var(--color-ink); }
/* muted/secondary utility + outline-secondary button text (literal #6c757d) */
.text-muted { color: var(--color-ink-muted); }
.text-secondary { color: var(--color-ink-secondary); }
.btn-outline-secondary { --bs-btn-color: var(--color-ink-secondary); --bs-btn-hover-color: var(--color-on-accent); color: var(--color-ink-secondary); border-color: var(--color-line); }

/* PM-010 / BU-376: .neo-btn shipped with NO style rule anywhere, so it fell back to
   raw browser button chrome (the #efefef UA face) in every theme — invisible to any
   reskin. Author it token-native so it reskins like the rest of the system. */
.neo-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-line);
  color: var(--color-ink);
  border-radius: var(--radius-sm, 6px);
  padding: 6px 12px;
  font: inherit;
  line-height: 1.2;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease;
}
.neo-btn:hover { background: var(--color-hover, var(--color-surface-raised)); border-color: var(--color-line-strong, var(--color-line)); }
.neo-btn:disabled { opacity: .55; cursor: not-allowed; }
.neo-btn-sm { padding: 4px 10px; font-size: 0.8rem; }
.neo-btn-primary {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-on-accent);
}
.neo-btn-primary:hover { background: var(--color-accent-hover, var(--color-accent)); border-color: var(--color-accent-hover, var(--color-accent)); }
.btn-outline-secondary:hover, .btn-outline-secondary:active, .btn-outline-secondary.active { color: var(--color-on-accent); }
/* BU-532: two more Bootstrap button variants surfaced by the /plan/generator gate.
   .btn-secondary hardcodes gray-600 fill/border; .btn-outline-dark hardcodes near-black
   text/border — both frozen under a vibe swap. Route to tokens like the others. */
.btn-secondary { --bs-btn-bg: var(--color-ink-secondary); --bs-btn-border-color: var(--color-ink-secondary); --bs-btn-hover-bg: var(--color-ink-dim); --bs-btn-hover-border-color: var(--color-ink-dim); --bs-btn-active-bg: var(--color-ink-dim); --bs-btn-active-border-color: var(--color-ink-dim); --bs-btn-color: var(--color-surface); --bs-btn-hover-color: var(--color-surface); --bs-btn-active-color: var(--color-surface); background-color: var(--color-ink-secondary); border-color: var(--color-ink-secondary); color: var(--color-surface); }
.btn-outline-dark { --bs-btn-color: var(--color-ink); --bs-btn-border-color: var(--color-line-strong); --bs-btn-hover-bg: var(--color-ink); --bs-btn-hover-border-color: var(--color-ink); --bs-btn-hover-color: var(--color-surface); --bs-btn-active-bg: var(--color-ink); --bs-btn-active-color: var(--color-surface); color: var(--color-ink); border-color: var(--color-line-strong); }

/* hardcoded light-gray borders -> line token so dividers/tables/inputs theme */
.table, .table > :not(caption) > * > *,
.form-control, .form-select, .input-group-text,
.dropdown-menu, .nav-tabs, .modal-header, .page-link, .img-thumbnail { border-color: var(--color-line); }
/* Bootstrap hardcodes surface bg literals on .table-light thead (--bs-table-bg:#f8f9fa),
 * .form-check-input (#fff), .dropdown-menu (#fff) — route to surface tokens so table
 * headers / checkboxes / menus theme. :checked checkbox keeps its accent fill. */
.table-light { --bs-table-bg: var(--color-surface-cool); --bs-table-color: var(--color-ink); color: var(--color-ink); border-color: var(--color-line); }
/* BU-389: Bootstrap's thead-divider border-bottom defaults to currentColor (=ink),
 * so the header column dividers never repaint per vibe (reskin border gate flags
 * al-col-*/ml-col-* on voices/assets/media). Route to the heavier line token. */
.table > :not(:last-child) > :last-child > * { border-bottom-color: var(--color-line-strong); }
/* BU-391: the :not(:last-child):last-child structural selector above clears the
 * /voices table but not /assets, /media, /role (their section structure does not
 * match it). Target thead cells' border-bottom directly so every list-table header
 * divider repaints via the heavier line token (default #d1d5db -> sepia #6b5d49,
 * large delta) regardless of table-section layout. */
.table > thead > tr > th, .table > thead > tr > td { border-bottom-color: var(--color-line-strong) !important; }
.table { --bs-table-bg: transparent; }
.form-check-input:not(:checked) { background-color: var(--color-surface); }
.dropdown-menu { background-color: var(--color-surface); }
.list-group-item { background-color: var(--color-surface); }
.page-link { background-color: var(--color-surface); }
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link { background-color: var(--color-surface); color: var(--color-ink); border-color: var(--color-line) var(--color-line) var(--color-surface); }
.progress { background-color: var(--color-surface-sunken); }
.modal-content { background-color: var(--color-surface); }
.offcanvas { background-color: var(--color-surface); }
/* Bootstrap .alert-* hardcode bg/border/text literals (#e2e3e5 etc.) — bridge the
 * neutral secondary alert; colored alerts (success/danger/warning/info) stay signal. */
.alert-secondary { background-color: var(--color-surface-sunken); border-color: var(--color-line); color: var(--color-ink); }
/* Bootstrap outline buttons hardcode their hue as text color (#dc3545 etc.) — illegible on
 * dark vibes. Lighten on dark via --signal-amt (0% on light = unchanged); border stays the
 * signal hue, hover keeps white-on-fill. */
.btn-outline-primary { color: color-mix(in srgb, #0d6efd, var(--signal-tone) var(--signal-amt)); }
.btn-outline-danger  { color: color-mix(in srgb, #dc3545, var(--signal-tone) var(--signal-amt)); }
.btn-outline-success { color: color-mix(in srgb, #198754, var(--signal-tone) var(--signal-amt)); }
.btn-outline-info    { color: color-mix(in srgb, #0dcaf0, var(--signal-tone) var(--signal-amt)); }
/* BU-392: on the cream sepia surface the info color-mix resolves to #177d8d teal (4.0:1,
   under AA-normal). Darken the info text on sepia to #146570 (6.2:1 on cream). Other vibes
   keep the mix (AA on their surfaces). */
:root[data-theme="sepia-ledger"] .btn-outline-info { color: #146570; }
/* BU-390: Bootstrap .bg-dark is a fixed near-black that ignores the vibe. Route the
   dark badge to the per-vibe invert tokens so it flips with the skin (dark on a light
   vibe, light on a dark vibe) instead of staying locked dark. */
.badge.bg-dark {
    background-color: var(--color-invert-bg) !important;
    color: var(--color-invert-ink) !important;
}
.btn-outline-warning { color: color-mix(in srgb, #946200, var(--signal-tone) var(--signal-amt)); }
.btn-outline-primary:hover,.btn-outline-danger:hover,.btn-outline-success:hover,.btn-outline-info:hover,.btn-outline-warning:hover { color: var(--color-on-accent); }

/* BU-532: Bootstrap's .bg-white utility hardcodes white with !important
   (gantt label columns on /plan/generator). Re-route it to the surface token
   so dark vibes retheme it; !important + load-after wins the tie. */
.bg-white { background-color: var(--color-surface) !important; }

/* ============================================================================
 * BU-823 — THE SMALL-VIEWPORT DENSITY LAYER
 * ----------------------------------------------------------------------------
 * Beau, 2026-08-20: "you turn it into what feels like a kid's toy almost, with
 * big letters and big shapes. it looks coarse and cheap... you're
 * underestimating how much people can read on a phone. font sizes don't actually
 * have to be that big anymore... it looks better when shapes and icons are
 * relatively small."
 *
 * WHY THIS FILE. index.html links jgn-components.css on every route, and this
 * file already owns the shared control bar every library page sits on. One layer
 * here lands on /plans, /assets, /media, /templates, /voices and /assign at once
 * instead of being written thirteen times in thirteen page stylesheets.
 *
 * WHAT THE DEFECT ACTUALLY IS, measured on /assets (2026-08-21, 30246 elements
 * walked at both widths): every number is IDENTICAL at 390 and at 1440. h1 28px
 * / row titles 16px / buttons 16px text in a 46px box / search field 44px tall /
 * type well 32px square - the same at both. The page has no downward scaling at
 * all. What it does have is upward inflation to a 44px touch floor
 * (responsive.css:99-106, and min-height:44px on .asset-input,
 * .asset-select-trigger and .asset-stage-strip__segment). So the defect is the
 * ABSENCE of a small-viewport scale, not the presence of an oversized one, and
 * this layer supplies the missing scale. The inflating rule is retired at source
 * in responsive.css.
 *
 * WHY !important, and why that is not laziness. index.html links this file at
 * line 28 and the CSS-isolation bundle Jargan.Client.styles.css at line 45. Every
 * page rule is scoped, so `.asset-btn[b-50kfulyq84]` carries one class PLUS one
 * attribute and outranks a plain `.asset-btn` written here however late it
 * loads. Load order cannot break that tie - only specificity or importance can.
 * The alternative to !important is not tidier CSS, it is editing thirteen page
 * stylesheets, which is the duplication this unit exists to prevent.
 *
 * THE THREE RULES THIS LAYER IS BUILT AROUND (JarganApiDemo/scripts/density-gate.mjs):
 *   CEILING  no element may exceed its OWN computed value at 1440.
 *   FLOOR    no rendered text below 12px (11px for the label role).
 *   CUT OFF  zero overflow, zero off-canvas, zero clipped, at every viewport.
 *
 * CEILING + FLOOR TOGETHER DECIDE WHERE EACH RULE LIVES, and that is the one
 * non-obvious thing about this block. A rule that makes text BIGGER cannot live
 * inside a max-width query: lifting a 9px mark to 12px on a phone while desktop
 * keeps 9px is a ceiling violation by construction. So the floor is stated at
 * EVERY width - it is a house legibility rule, not a phone rule - and only the
 * caps live under the breakpoint. Desktop is the reference, and the only thing
 * this layer changes there is text that was never readable at any width.
 *
 * BREAKPOINT: 900px, the one this file already declares (the band block above)
 * and the most-declared width in Jargan.Client. Standardising the 11 live
 * breakpoints is a different card and is deliberately not attempted here.
 *
 * COLOUR: none. Not one declaration below sets a colour, so nothing here can
 * introduce a raw literal; the --v-* semantic token layer is untouched.
 * ============================================================================ */

/* ---- 1. THE READABILITY FLOOR - stated at every width ----------------------
   Measured below the floor on /assets: .asset-chip 10.5px (3933 marks),
   .asset-stage-dot 9px (2544), .asset-kicker 11px. Each of those carries real
   meaning - a clearance, a status, a funnel stage, the page's eyebrow - and 9px
   is not a design decision anyone can read on a phone. 12px is the floor for
   every role but `label`, and it also sits inside the `ui` cap of 13px, so one
   value satisfies the floor and the scale at the same time.

   Deliberately NOT in this list: the label role (`label`, .*-label, .*__label,
   eyebrow, overline). Its house value is 11px and its floor is 11px - 3194
   elements on /assets already render at exactly 11px, and lifting them to 12
   would break the cap this same gate applies to them. */
:is([class*="chip"], [class*="badge"], [class*="pill"],
    [class*="dot"], [class*="kicker"]) {
    font-size: var(--text-caption) !important;   /* 12px at every width */
}

/* A glyph too small to read fails the same ask an oversized one does. The
   sidebar collapse chevron measured 13px at 768 - under the gate's 16px icon
   floor - and 13px at 1440 too, so it is raised at EVERY width; a phone-only
   raise is what the ceiling rule exists to catch. */
.mc-side__collapse svg {
    width: 16px !important;
    height: 16px !important;
}

@media (max-width: 900px) {

    /* ---- 2. THE HOUSE SCALE: the caps ---------------------------------------
       body 14 - UI 13 - caption 12 - label 11 - h3 15 - h2 17 - h1 20.
       Every value comes from the breakpoint-aware --text-* tokens
       (jargan-semantic-tokens.css), so the scale has ONE definition and this
       layer only decides which element wears which role. The role names are the
       ones the gate assigns, so the stylesheet and the gate read one taxonomy
       rather than two similar ones.

       Headings are matched by TAG, which is what the gate grades them on.
       /assets: .asset-head__title is an <h1> at 28px, .asset-row__name is an
       <h3> at 16px on all 636 rows. */
    h1 { font-size: var(--text-h1) !important; }              /* 20px */
    h2 { font-size: var(--text-h2) !important; }              /* 17px */
    h3, h4, h5, h6 { font-size: var(--text-h3) !important; }  /* 15px */

    /* Controls carry the UI size and their contents inherit it. This one rule is
       what takes .asset-btn 16 -> 13 and, by inheritance, the unclassed <span>s
       inside .asset-select-trigger ("Any voice", the caret) and
       .asset-stage-strip__letter, none of which declare a size of their own. */
    button, [role="button"], input, select, textarea, optgroup, option {
        font-size: var(--text-ui) !important;                 /* 13px */
    }

    /* The display metric. A 30px number on a 390px phone is the "big shapes"
       complaint in its purest form (/assets: .asset-tile__value, a <p> at 30px
       at BOTH widths). Bounded to block-level elements on purpose: an inline
       <span class="...__value"> inside a chip is a chip, not a display metric,
       and capping that here could only ever RAISE it into a ceiling violation. */
    :is(p, div, dd, dt, strong, b)[class*="value"],
    :is(p, div, dd, dt, strong, b)[class*="metric"],
    :is(p, div, dd, dt, strong, b)[class*="readout"] {
        font-size: var(--text-body) !important;               /* 14px */
    }

    /* Caption role. /assets: .asset-tile__meta 13px, and the footer's
       .text-muted / .text-muted.small run at 16px and 14px. */
    small, figcaption, .small, .text-muted,
    [class*="caption"], [class*="__meta"], [class*="-meta"],
    [class*="hint"], [class*="help"], [class*="subtitle"],
    [class*="footnote"], [class*="muted"] {
        font-size: var(--text-caption) !important;            /* 12px */
    }

    /* Label role - the scale's base, and the one role whose floor is 11 rather
       than 12. Stated after the caption rule so a "__label" never lands on 12. */
    [class*="eyebrow"], [class*="overline"],
    [class*="__label"], [class*="-label"] {
        font-size: var(--text-label) !important;              /* 11px */
    }

    /* A bare <label> takes the label size ONLY when it is itself the microlabel.
       Measured: `label > span "Select All"` on /assets is a checkbox's caption,
       and 11px cascading into that span put it under the 12px floor - the span
       carries no label class, so the gate reads it as body text and is right to.
       :not(:has(*)) draws the line where the DOM already draws it: a label with
       no element children is a microlabel, a label that wraps a control is a
       form row and keeps the size it inherits. */
    label:not(:has(*)) {
        font-size: var(--text-label) !important;              /* 11px */
    }

    /* ---- 3. CONTROLS: 36px, not 44 -----------------------------------------
       Scoped to <main> ON PURPOSE, and this is the part that would be wrong if
       it were global. Measured in the app shell at 1440: the sidebar collapse
       button is 26px, .mc-workspace 30.5px, .mc-search__input 21.5px. The shell
       already runs its own compact scale - every one of those is ALREADY under
       the house 36px - so a blanket control height would not shrink them, it
       would GROW them, and the ceiling rule forbids exactly that. The coarse
       controls are all in the page body, so that is where this applies.

       min-height is zeroed first: on /assets the 44px comes entirely FROM
       min-height (.asset-input carries zero padding - its whole box is the touch
       floor), so the box has to be rebuilt from padding once the floor is
       lifted, not merely trimmed. */
    main :is(button, [role="button"], input, select, textarea) {
        min-height: 0 !important;
    }
    /* Real <button> elements only. [role="button"] is deliberately absent: it is
       worn by inline marks as well as by chrome - measured, <span
       class="chip-segment chip-segment--all" role="button"> is 14px tall at 1440
       - and rebuilding a padding box under one of those GROWS it, which is a
       ceiling violation and a worse page. Zeroing the inflated min-height above
       is safe for every control because it can only ever shrink; rebuilding the
       box is not, so it is applied where the coarse boxes actually are. */
    main button {
        padding-block: 8px !important;
        line-height: var(--leading-ui) !important;   /* 13 x 1.4 = 18.2 */
    }
    /* THE BOX REBUILD IS SCOPED TO THE LIBRARY SHELL, and that scope is measured,
       not stylistic. Zeroing a min-height can only ever SHRINK a control, so it is
       safe on every page; REBUILDING a box out of padding can GROW one, and a
       growth at 390 is a ceiling violation by construction.

       Measured at 390 with this layer stripped from the live CSSOM: /assign and
       /plans render `input.jgn-search__input` at 20.19px against a desktop value
       of 21.59px. Those pages already ship a compact control scale and have no
       44px floor to retire, so a blanket min-height:36px did not shrink them - it
       took them from 20px to 36px, i.e. it re-created on /assign the exact "big
       shapes" defect this unit exists to remove, and the gate caught it as
       `ceiling <input.jgn-search__input> measured 36px expected <= 21.59px`.

       The inflated boxes all live inside the library page shells. Measured, one
       route at a time: .asset-input / .asset-select inside .asset-shell,
       .media-input inside .media-shell, .vlb-input inside .vlb-shell, .tpl-select
       inside .tpl-shell - every one of them 44px before this layer, every one of
       them inside an element whose class ENDS in "-shell". /plans and /assign have
       no shell element at all. So the shell is the measured boundary between "was
       inflated to a touch floor" and "was already compact", and scoping to it is
       what lets ONE shared rule shrink the coarse pages without growing the
       compact ones. */
    main [class$="-shell"] :is(input, select, textarea):not([type="checkbox"]):not([type="radio"]):not([type="range"]) {
        min-height: 36px !important;
        padding-block: 6px !important;
    }

    /* Shell chrome that is not inside <main> and therefore has to be named. The
       vibe toggler is built in JS (index.html) with min-height/min-width:44px as
       an INLINE style, so nothing but !important can reach it - the button's size
       is not "unfixable from CSS", it just needs the right lever. The phone
       hamburger is a fixed 44px square in MainLayout.razor.css. */
    #jgn-vibe-toggler {
        min-height: 32px !important;
        min-width: 0 !important;
        height: 32px !important;
        padding: 0 10px !important;
    }
    .app-shell__mobilenav {
        top: 6px !important;
        width: 32px !important;
        height: 32px !important;
    }

    /* ---- 4. ICONS: 16-18px --------------------------------------------------
       The sidebar brand mark measured 22px. */
    .mc-side__brand svg {
        width: 18px !important;
        height: 18px !important;
    }

    /* ---- 5. The asset row's type well --------------------------------------
       A 32px square holding a 10.5px three-letter code (IMG / VID / TXT) on all
       636 rows. It cannot satisfy both halves of the ask at once: its class calls
       it an icon, so it is graded as a glyph at 16-18px, while its text is graded
       against the 12px readability floor - and the code measures 19.45px of ink
       at 12px even with the tracking pulled to -0.06em, so it cannot be both.

       It does not need to be. The same fact is already stated in words by the
       type chip on the same row: measured 636 rows, 636 type wells, 636
       .asset-chip--type chips, first row "IMG" beside the chip "Photo". At phone
       width the well is redundant chrome, and dropping it returns ~40px of row
       width to the thing the row is actually about - the asset's name. Desktop
       keeps the well; this is a phone decision, and hiding is a shrink, never a
       growth, so the ceiling is satisfied. */
    .asset-row__icon {
        display: none !important;
    }
}

/* ============================================================================
 * BU-823 — DENSITY LAYER, PASS 2: what a fresh reviewer found at 390
 * ----------------------------------------------------------------------------
 * The layer above passed the density gate at 390, 768 and 1440 and then FAILED
 * a fresh-eyes visual review with three findings. All three are recorded here
 * verbatim-in-substance, because each one names a defect the numeric gate is
 * structurally unable to see - which is the whole reason a human-shaped review
 * sits behind the gate:
 *
 *   1. "Clearance field (globe icon input) renders with no placeholder text or
 *      label content, unlike every sibling filter (Search: 'Type to filter by
 *      asset name...', Voice: 'Any voice', Status: 'All statuses'). It reads as
 *      an empty/unstyled control."
 *   2. "The orange 'default' status pill is absolutely positioned so it overlaps
 *      the top-right corner of the 'Total Assets' card and sits flush against
 *      the card's bottom text line with almost no clearance... reposition it out
 *      of the card's bounding box."
 *   3. "The Total Assets card is cut off with no visible bottom border/padding
 *      closure before the viewport edge... making the bottom look unfinished."
 *
 * None of the three is a font-size. The gate measures type, control boxes,
 * overflow and clipping; it cannot measure "this control looks empty", and a
 * position:fixed pill sitting on a card is neither overflow nor clipping, so
 * both scans reported clean. They are fixed here, in the shared file, for the
 * same reason the layer above lives here: /plans, /assets, /media, /templates,
 * /voices and /assign all mount this stylesheet.
 * ============================================================================ */

/* ---- 1. THE CLEARANCE FILTER HAS NO WORDS IN IT ----------------------------
 * ClearanceSelector.razor collapses an unset (global) clearance to a bare Lucide
 * globe - "BU-186 bullet 66 (2026-05-18): 'Clearance' label removed; 'All' value
 * collapsed to a Lucide globe icon". Measured on /assets at 390: the host box is
 * a full-width field and the chip inside it is 14x14. Every sibling filter in
 * the same stack states its resting value in words ("Any voice", "All statuses",
 * "Type to filter by asset name..."), so the clearance cell alone reads as an
 * input that failed to render.
 *
 * This is not a new opinion. MediaLibraryPage.razor.css:311 already recorded it
 * -- "Clearance is the one filter whose control renders no readable text of its
 * own -- an unset clearance is a bare 14px globe glyph" -- and answered it with a
 * visible label and a chevron, i.e. it made the BOX look like a picker but left
 * the VALUE unsaid. This rule says the value.
 *
 * WHY A PSEUDO-ELEMENT AND NOT THE COMPONENT. The .razor is outside this unit's
 * fence, and it is also the wrong place: ClearanceSelector is shared with the
 * compose workbench header, where the same globe sits in a text breadcrumb that
 * reads "Clearance / Acme / Q3" and wants no filler word at all. So the text is
 * attached to the FILTER-CELL context only - every host that spells its purpose
 * in its class name - and the breadcrumb keeps the bare glyph.
 *
 * Stated at every width on purpose: a phone-only placeholder would be a string
 * that appears when the window narrows, and the ceiling rule's spirit (desktop
 * is the reference, phones do not gain things) says the same word belongs at
 * 1440. The gate grades an element's OWN text nodes, so pseudo content is not
 * graded typography either way - it inherits .chip-segment--all's 13px UI size.
 * The three hosts this lands on: .asset-clearance-host (/assets),
 * .media-clearance-host (/media), .plan-library__clearancehost (/plans), plus
 * the design-system's .jgn-filters-card__clearance-host. */
:is([class*="clearance-host"], [class*="clearancehost"]) .chip-segment--all::after {
    content: "Any clearance";
}

/* ---- WHERE THE VIBE PILL GOES ---------------------------------------------
 * Finding 2, and the standing rule again: "If the button size can't change, then
 * you need to make sure that this environment and layout accommodate it."
 *
 * WHAT IT IS. #jgn-vibe-toggler is built in JS (index.html:187) as a
 * position:fixed pill at right:14px / bottom:14px, shown on localhost, staging
 * and *.ts.net. Measured at 390 before this rule: x 287.4-376, y 794-830 - the
 * page's cards run x 38-352, and .asset-tile__value ("636") occupies y 803-817.
 * So the pill was not near the card, it was ON the number.
 *
 * WHY IT CANNOT SIMPLY BE SHRUNK INTO THE MARGIN. The page keeps a 38px gutter
 * to the right of every card at 390 (50px at 768), so a pill narrow enough to
 * live in that gutter would have to drop its label - and the label is the whole
 * point of the control (BU-632 exists because Beau could not see the pill at
 * all over Tailscale). Hiding the text with font-size:0 is not available either:
 * the gate grades an element's own text nodes and would read 0px against a 12px
 * floor. Turning the pill vertical to fit the gutter fails the same gate from
 * the other side - writing-mode makes it ~70px TALL against a 36px control cap.
 *
 * WHERE THERE IS ACTUALLY ROOM. The app shell already reserves a top gutter that
 * no card ever enters: at 390 .app-shell__content starts at y=8 and the first
 * card at y=40; at 768 the first card starts at y=42. The phone hamburger
 * (.app-shell__mobilenav, fixed, top:10 right:10) is already parked in it. So
 * the pill moves into that same band, docked to the LEFT of the hamburger, and
 * both are taken to 32px so the pair sits inside the 40px gutter with clearance
 * instead of clipping the card's top edge as the 36px hamburger did. Measured
 * after: pill y 6-38 x 261-342, hamburger y 6-38 x 348-380, first card y 40.
 * Zero intersection with any card at either viewport - the finding's own
 * preferred remedy, "reposition it out of the card's bounding box".
 *
 * 640px is MainLayout.razor.css's own breakpoint for the hamburger (:146); the
 * pill only needs to dodge it where it exists, so above 640 the pill keeps the
 * right edge to itself. No new breakpoint is introduced by this file.
 *
 * The ring goes from --color-accent to --color-line at these widths. Traffic
 * Orange is the signal colour and a theme switcher is not a signal; at the
 * bottom of the page an orange ring was merely loud, in the header band it
 * would compete with the primary "+ New" action two rows below it. The accent
 * dot inside keeps the brand mark. Colour still routes through the semantic
 * token layer - measured resolving on default (#232a33), sepia-ledger
 * (#d8c9ad) and carbon-lime (rgba(184,192,200,.14)) - so no raw literal enters. */
@media (max-width: 900px) {
    #jgn-vibe-toggler {
        top: 6px !important;
        bottom: auto !important;
        right: 14px !important;
        border-radius: 16px !important;
        border-color: var(--color-line) !important;
        font-size: var(--text-caption) !important;   /* 12px, on the floor, under the 13px UI cap */
    }
}

@media (max-width: 640px) {
    /* Only where the hamburger is rendered (MainLayout.razor.css:156 declares it
       at <=640 and nowhere else) does the pill need to step aside for it:
       32px button + 10px inset + 6px gap. */
    #jgn-vibe-toggler {
        right: 48px !important;
    }
}

@media (max-width: 900px) {

    /* ---- 2. THE LAST OF THE 44px TOUCH FLOOR: INPUT SHELLS -----------------
       The layer above retired min-height:44px on <input>/<select>/<textarea> and
       on responsive.css's global .action-icon rule, and that was not all of it.
       The house pattern on these pages is a DIV that draws the field - border,
       radius, icon, padding - wrapping a borderless <input>; the 44px lives on
       the WRAPPER, so an input-only rule cannot see it. Measured on /assets at
       390 after pass 1: .asset-search 44px and .asset-clearance-host 44px, each
       holding a 36px control. Two 8px bands of nothing, on every library page.

       Matched by the shape of the class name rather than by page-specific names,
       so the same fix lands on /media (.media-clearance-host, also 44px) and on
       every other library page without being written three times. min-height only
       ever binds when the natural height is smaller, so on the field WRAPPERS this
       also matches (.asset-toolbar__search, natural height 57px) it is a no-op.

       Inside the library shell for the same measured reason as the control rule
       above: `[class*="-search"]` also matches the `jgn-search` chrome that /plans
       and /assign use, and those pages ship a 20px control by design - a 36px
       floor there is a growth, not a de-inflation, and the ceiling rule is right
       to call it one. */
    main [class$="-shell"] :is([class*="clearance-host"], [class*="clearancehost"],
              [class*="select-trigger"], [class*="__search"], [class*="-search"]) {
        min-height: 36px !important;
    }

    /* ---- 3. VERTICAL RHYTHM: the other half of "coarse" --------------------
       Type and control sizes were pass 1. The spacing between them was not, and
       at 390 it is where the remaining coarseness lives: measured on /assets,
       the page shell stacked its sections 18px apart inside 16px of its own
       padding, each card carried 18-20px of padding, and each label sat 8px off
       its control - every one of those numbers identical to the 1440 rendering,
       which is the same absence-of-downward-scaling the card describes.

       Desktop keeps all of it. At 390 the stack goes to a 12px rhythm and cards
       to 12/14 padding, which recovered 118px above the fold on /assets - the
       "Total Assets" card was being sliced 16px short of its own bottom border
       (finding 3) and now closes inside the viewport with the next card's first
       three rows visible below it, which reads as a page that continues rather
       than one that was truncated.

       Selectors are shape-based for the same reason as above: .asset-shell,
       .media-*, .plan-library__* and .vlb-* are four vocabularies for one
       layout. The element-name guards are load-bearing - :is(header, section,
       article) keeps the card-padding rule off .asset-head__identity (a DIV that
       also contains "-head"), and off the field wrapper .asset-toolbar__search
       (a DIV that contains "-toolbar"), where a 12px gap would have GROWN an 8px
       one and walked straight into the ceiling rule. */
    main [class$="-shell"] {
        gap: 12px !important;
        /* Sides and bottom go to the 12px rhythm; the TOP does not, and the
           asymmetry is load-bearing. This padding is the gutter the fixed chrome
           row is docked into (pill + hamburger, y 6-38), and the page shell's own
           box starts at y=22 (768) / y=24 (390), so anything under 16px here puts
           the first card's top edge underneath them. 18px leaves the card 2-4px
           of daylight at both widths - measured, not guessed. */
        padding: 18px 12px 12px !important;
    }
    main :is(header, section, article)[class*="-head"],
    main :is(header, section, article)[class*="-toolbar"],
    main :is(article, li)[class*="-tile"] {
        padding: 12px 14px !important;
    }
    main :is(header, section, article)[class*="-toolbar"] {
        gap: 12px !important;
    }
    /* label-to-control, and icon-to-input inside a field shell */
    main :is([class*="-field"], [class*="__field"], [class*="__search"], [class*="-search"]) {
        gap: 6px !important;
    }
    /* filter stacks and button rows */
    main :is([class*="__filters"], [class*="-filters"],
             [class*="__actions"], [class*="-actions"]) {
        gap: 8px !important;
    }

    /* ---- 4. ONE COLUMN, ONE WIDTH: the filter stack ------------------------
       The fresh-eyes review read the clearance cell as "an empty/unstyled
       control" partly because it did not look like its siblings, and the widths
       are the other half of that. Measured on /assets at 390 inside
       .asset-toolbar__filters: search 292px, voice 160px, status 160px,
       clearance 240px, stages 292px - five fields, four different widths, each
       already on its own row (the labels VOICE / STATUS / CLEARANCE stack, they
       do not sit side by side), so the ragged right edge buys nothing. Every
       field is already the full width of a row; only its control was not.

       This is a width statement only. The fields were stacked before and are
       stacked after, so no row count, no height and no font size moves - the
       density gate reads exactly the same numbers, and the bar reads as one
       column of controls rather than four sizes of leftover. min-width:0 is what
       lets a flex item actually reach 100% instead of resting at its content
       width. */
    main [class$="-shell"] :is([class*="__filters"], [class*="-filters"]) > * {
        flex: 1 1 100% !important;
        width: 100% !important;
        min-width: 0 !important;
    }
}
