/* Console: the settings vocabulary. A panel of rows, each row a statement with
   its control at the end of it. See admin/src/views/panel.ts for why.

   Every value is a --cb-* token. The exceptions are the ones the token scale
   deliberately does not model: hairlines (1px), 100%/0, ch widths for inputs
   sized to their content, and breakpoint literals in media queries. */

.cb-panel {
  background: var(--cbc-surface);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-3xl);
  overflow: hidden;
}
/* The same air between cards as the dashboard leaves between its own, which
   is the gap on .cb-flow. Panels were getting both: a twenty-pixel flow gap
   and a twenty-four pixel margin on top of it, so a settings screen stood its
   cards forty-four pixels apart while the board next door used twenty. The
   margin is for panels stacked outside a flow; inside one the gap is already
   doing it. */
.cb-panel + .cb-panel {
  margin-top: var(--cb-space-2xl);
}
.cb-flow > .cb-panel + .cb-panel {
  margin-top: 0;
}

/* A step in shade, not a hole. The reason this had no fill was that a darker
   strip on a dark ground reads as a hole punched through the card; --cbc-hover
   is the layer built for exactly this problem, a translucent ink wash that
   lightens on the dark theme and darkens on the light one, so the header sits
   on top of the card in both rather than being cut out of one of them. */
/* Tighter than it was. A panel header carrying a title and a paragraph at full
   card padding put a wide stripe of chrome above every group of settings, and
   on a screen with five groups that is more furniture than content. */
.cb-panel-head {
  display: flex;
  align-items: flex-start;
  gap: var(--cb-space-2xl);
  padding: var(--cb-space-2xl) var(--cb-space-3xl);
  border-bottom: 1px solid var(--cbc-line);
  background: var(--cbc-hover);
}
.cb-panel-head h2 {
  font-size: var(--cb-type-md-size);
  line-height: var(--cb-type-md-line-height);
  font-weight: var(--cb-font-weight-semibold);
}
.cb-panel-head p {
  margin-top: var(--cb-space-xs);
  max-width: var(--cb-layout-paragraph-max-width);
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-type-sm-line-height);
  color: var(--cbc-ink-faint);
}
.cb-panel-actions {
  margin-inline-start: auto;
  display: flex;
  gap: var(--cb-space-lg);
}

/* ---- One setting ----
   A grid, not space-between. Pushing the control to the far edge of whatever
   width the row happens to have is what made these screens read as two
   unrelated columns: the label and the thing it names ended up a paragraph
   apart. Here the control column is a fixed width sitting directly after the
   words, so a panel of settings has a column of controls and the eye can run
   down it. */
.cb-set {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--cbc-control-col);
  align-items: center;
  gap: var(--cb-space-3xl);
  /* A slider row carries a track, a ruler and two end labels, so at the old
     padding it stood eighty-two pixels tall against a menu row's seventy, and
     the settings screens read at two different densities. */
  padding: var(--cbc-set-y) var(--cb-space-3xl);
}
/* A control that needs the room (a rule builder, a chip list) takes the row. */
.cb-set-stack {
  grid-template-columns: minmax(0, 1fr);
}
/* And one that needs more than the column but not the row: a web address, cut
   off mid-domain at the standard width. */
.cb-set[data-wide] {
  grid-template-columns: minmax(0, 1fr) minmax(0, var(--cb-width-sm));
}
.cb-set + .cb-set,
.cb-route-row + .cb-route-row,
.cb-expand + .cb-expand,
.cb-set + .cb-remark {
  border-top: 1px solid var(--cbc-line);
}

.cb-set-label {
  min-width: 0;
}
.cb-set-name,
.cb-set-name > label {
  font-size: var(--cb-type-sm-size);
  font-weight: var(--cb-font-weight-medium);
  cursor: default;
}
.cb-set-name > label {
  cursor: pointer;
}
.cb-set-note {
  margin-top: var(--cb-space-xxs);
  max-width: var(--cb-layout-paragraph-max-width);
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-type-sm-line-height);
  color: var(--cbc-ink-faint);
}
.cb-set-control {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--cb-space-lg);
  min-width: 0;
}
/* Controls fill their column rather than sitting at whatever width their
   content happens to be, so the left edges line up down the panel. */
.cb-set-control > .cb-pick-wrap,
.cb-set-control > .cb-input,
.cb-set-control > .cb-slider-wrap,
.cb-set-control > .cb-range-wrap {
  width: 100%;
}

/* A setting that only applies while something else is on. Dimmed and inert
   rather than hidden: knowing the field exists is worth more than a tidy
   panel, and a field you can fill in that will never be read is a trap. */
/* Eased, and unconditionally so it works in both directions. The dim is the
   only thing saying these rows belong to the switch you just moved, and a
   relationship delivered in zero frames is a relationship nobody sees drawn. */
.cb-set {
  transition: opacity var(--cb-motion-base) var(--cbc-spring);
}
.cb-set[data-depends="off"] {
  opacity: 0.5;
}
/* The same dim on a line of a service sheet, which is a cb-spec-line rather
   than a cb-set and can be turned off for the same reason: a ranking every
   audit has taken over is not in force (views/service-ranking.ts). Without it
   the control went inert carrying the sentence saying why and still looked
   exactly like one somebody could use. No transition beside it, unlike the row
   above: that dim answers a switch being moved, while this one is decided as
   the row is built and is already there by the time anybody sees it. */
.cb-spec-line[data-depends="off"] {
  opacity: 0.5;
}
/* No pointer-events: none. It stops the row carrying the tooltip that says why
   it is off, which is the whole reason rule 15 turns controls off rather than
   removing them. The controls inside are turned off individually instead. */

/* ---- The hundred ----
   The scoring weights, drawn once as the thing they are: a hundred, split
   eight ways. What was here before was a thin read-only ribbon over eight
   sliders, which is two pictures of one quantity where the better looking one
   could not be touched. The bar is the control now (admin/src/views/weight-bar.ts),
   and everything below is what that needs to read correctly at a glance.

   One continuous capsule rather than eight separated chips: these are shares
   of one thing, and gaps between them say they are eight things. The cuts
   between shares are the drag handles, so the only divisions drawn are the
   ones you can move. */
.cb-weights {
  display: grid;
  gap: var(--cb-space-xl);
  padding: var(--cb-space-3xl);
  border-bottom: 1px solid var(--cbc-line);
}

/* The running total, above the bar where the eye lands first. It is the one
   thing on this panel that can be saved wrong. */
.cb-weights-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--cb-space-2xl);
  min-block-size: var(--cb-space-4xl);
}
.cb-weights-total {
  font-size: var(--cb-type-lg-size);
  font-weight: var(--cb-font-weight-semibold);
  font-variant-numeric: tabular-nums;
  transition: color var(--cb-motion-base) var(--cbc-spring);
}
.cb-weights-head[data-tone="ok"] .cb-weights-total {
  color: var(--cbc-good-text);
}
.cb-weights-head[data-tone="warn"] .cb-weights-total {
  color: var(--cbc-accent);
}
.cb-weights-note {
  margin-inline-start: var(--cb-space-lg);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}
/* What the pointer is on, or the standing instruction when it is on nothing.
   Reserved height, so reading one share does not shift the panel. */
.cb-weights-aim {
  min-block-size: var(--cb-space-2xl);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}

.cb-weights-bar {
  position: relative;
  block-size: var(--cb-space-7xl);
}
.cb-weights-run {
  display: flex;
  block-size: 100%;
  border-radius: var(--cb-radius-2xl);
  background: var(--cbc-sunken);
  overflow: hidden;
  /* The entrance, and there is only one of it. The bar sweeps open as a single
     object; the labels ride in behind the sweep (see .cb-weight-face). Eight
     segments each animating themselves read as eight things arriving, which is
     the opposite of what this bar is trying to say. */
  animation: cbc-wipe-x var(--cb-motion-slow) var(--cbc-spring) backwards;
}

/* ---- One share ---- */
.cb-weight {
  position: relative;
  flex: 0 0 var(--share, 0%);
  min-inline-size: 0;
  /* The button reset used to carry `padding: 0` here and it never applied: the
     real padding is set again fourteen lines down and a later declaration of
     the same property wins outright. `border: 0` is the half of the reset that
     is doing work. */
  border: 0;
  overflow: hidden;
  text-align: start;
  cursor: pointer;
  /* Its own colour, handed down from the theme layer as --tint. One hue
     stepped eight ways says these are ordered, more of something at one end
     and less at the other, which is untrue of Speed against Accessibility.
     A spectrum says they are different, which is the only claim being made. */
  padding: var(--cb-space-lg) var(--cb-space-xl);
  color: var(--cbc-on-tint);
  background: var(--tint, var(--cbc-accent));
  transition:
    flex-basis var(--cb-motion-base) var(--cbc-spring),
    filter var(--cb-motion-fast) var(--cbc-spring);
}
/* The padding is on the share, not on the label block, so the room a label
   has to fit into is the room inside the padding. The bar measures against
   this: a label that would touch the edge is dropped, not squeezed. */
.cb-weight-face {
  display: grid;
  gap: var(--cb-space-xxs);
  min-inline-size: 0;
  white-space: nowrap;
  /* Arrives as the sweep above reaches it. --enter is where this share starts
     along the bar, 0 to 1, so labels appear left to right at the speed the bar
     is actually opening rather than on a stagger that only looks like it.

     Squared, because the sweep is eased and front-loaded: it covers three
     quarters of the bar in the first fifth of its time. Spacing the labels
     evenly against a curve that is anything but even left the last of them
     still arriving a third of a second after the bar had finished. */
  animation: cbc-emerge var(--cb-motion-base) var(--cbc-spring) backwards;
  animation-delay: calc(
    var(--cb-motion-base) *
    var(--enter, 0) *
    var(--enter, 0) +
    var(--cb-motion-fast) /
    3
  );
}
.cb-weight-value {
  font-size: var(--cb-type-md-size);
  font-weight: var(--cb-font-weight-semibold);
  font-variant-numeric: tabular-nums;
}
.cb-weight-name {
  font-size: var(--cb-type-xs-size);
  opacity: 0.72;
}
/* Three states, decided by measurement rather than by a guessed pixel width
   (see weight-bar.ts). Too narrow for the name and it keeps the number; too
   narrow for the number too and it says nothing and stays a colour. Clipped
   text is never an option: half a word is worse than no word. */
.cb-weight[data-tight="true"] {
  padding-inline: var(--cb-space-md);
}
.cb-weight[data-tight="true"] .cb-weight-name {
  display: none;
}
.cb-weight[data-bare="true"] .cb-weight-face,
.cb-weight[data-empty="true"] .cb-weight-face {
  visibility: hidden;
}

/* Aiming at a share holds the others back rather than recolouring the one you
   are on: a share that changes colour under the pointer reads as a different
   share, and these colours are now the only thing telling them apart. The same
   answer whether the pointer is on the bar or on the slider row below it, so
   the two are visibly one setting. */
.cb-weights-bar[data-aimed] .cb-weight {
  filter: grayscale(0.92);
}
.cb-weights-bar[data-aimed] .cb-weight[data-lit="true"] {
  filter: none;
}

/* ---- The shortfall and the excess ----
   The bar is scaled to the target while the shares fit inside it, so a total
   under a hundred leaves real empty track at the end and the gap is the
   missing number. Over a hundred the scale gives way, the gate line comes in
   from the right edge, and what spills past it is drawn in the warning colour
   rather than in the bar's own. */
.cb-weights-tail {
  flex: 0 0 var(--share, 0%);
  display: flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 0;
  overflow: hidden;
  white-space: nowrap;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  transition: flex-basis var(--cb-motion-base) var(--cbc-spring);
}
/* Hatched rather than washed, and in the one colour that is not in the
   spectrum: scarlet appears nowhere among the eight shares, so a scarlet hatch
   cannot be mistaken for one of them. A hatch is read as "this area does not
   count" everywhere it is used, and the shares still show through it. */
.cb-weights-over {
  position: absolute;
  inset-block: 0;
  inset-inline-start: var(--at, 100%);
  inline-size: var(--share, 0%);
  border-start-end-radius: var(--cb-radius-2xl);
  border-end-end-radius: var(--cb-radius-2xl);
  background: repeating-linear-gradient(
    135deg,
    color-mix(in srgb, var(--cbc-accent) 85%, transparent) 0 var(--cb-space-xs),
    transparent var(--cb-space-xs) var(--cb-space-lg)
  );
  pointer-events: none;
}

/* The target itself, drawn as a line across the bar with its number on the
   cap. Without it "over" and "under" are two words; with it they are a place. */
.cb-weights-gate {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: var(--at, 100%);
  block-size: 100%;
  inline-size: 2px;
  translate: -1px 0;
  background: var(--cbc-accent);
  transition: inset-inline-start var(--cb-motion-base) var(--cbc-spring);
}
/* Two pixels is not something anyone can point at, so the line carries a hit
   area wide enough to hover for its tooltip. */
.cb-weights-gate::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 50%;
  inline-size: var(--cb-space-xl);
  translate: -50% 0;
}
/* The number sits to the left of its line rather than centred on it: centred,
   it runs off the panel every time the line is at the end of the bar. */
.cb-weights-gate span {
  position: absolute;
  inset-block-end: calc(100% + var(--cb-space-xs));
  inset-inline-end: 0;
  padding-inline-end: var(--cb-space-sm);
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--cbc-accent);
}

/* ---- The cut between two shares ----
   A real focusable separator, so the trade is on the keyboard as well as under
   the pointer. The hit area is wider than the line it draws, because a two
   pixel target is a target nobody hits. */
.cb-weights-grip {
  position: absolute;
  inset-block: 0;
  inset-inline-start: var(--at, 0%);
  inline-size: var(--cb-space-3xl);
  translate: -50% 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: col-resize;
  touch-action: none;
  transition: inset-inline-start var(--cb-motion-base) var(--cbc-spring);
}
.cb-weights-grip::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 50%;
  inline-size: 2px;
  translate: -1px 0;
  background: var(--cbc-surface);
  transition: opacity var(--cb-motion-fast) var(--cbc-spring);
}
/* The handle only appears when it is wanted. Eight permanent grab bars across
   the bar would be the loudest thing on the screen and the least often used. */
.cb-weights-grip-pill {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  translate: -50% -50%;
  display: grid;
  place-items: center;
  inline-size: var(--cb-space-3xl);
  block-size: var(--cb-space-4xl);
  border-radius: var(--cb-radius-full);
  /* Dark in both themes, because the bar it sits on is pale in both. A handle
     the same lightness as the thing it is over is one nobody can see. */
  color: var(--cb-color-ceramic-grey);
  background: var(--cbc-on-tint);
  box-shadow: var(--cbc-shadow);
  opacity: 0;
  scale: 0.8;
  transition:
    opacity var(--cb-motion-fast) var(--cbc-spring),
    scale var(--cb-motion-fast) var(--cbc-spring);
}
/* Size the icon box and stop there. Overriding the glyph inside it left a
   16px mark sitting in the top-left corner of a 20px box, which is what put
   the handle a couple of pixels off centre in both directions. */
.cb-weights-grip-pill .cb-icon {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}
/* One handle at a time, the one being aimed at. Seven grab bars standing up
   across the bar the moment the pointer enters it was the loudest thing on the
   screen and covered the numbers it was meant to help you move. */
.cb-weights-grip:hover .cb-weights-grip-pill,
.cb-weights-grip:focus-visible .cb-weights-grip-pill,
.cb-weights-grip[data-dragging] .cb-weights-grip-pill {
  opacity: 1;
  scale: 1;
}
/* Held, not recoloured. Turning the handle accent on an accent bar is the one
   moment it needs to be easiest to see and it disappears instead. */
.cb-weights-grip[data-dragging] .cb-weights-grip-pill {
  scale: 1.1;
}
/* Nothing eases while a handle is under the finger: a bar that catches up
   half a second later is a bar that is not being dragged.

   AND A SLIDER IS A FINGER TOO, which is the half this missed. The rule asked
   only whether a GRIP was being dragged, so dragging one of the eight tracks
   underneath left the segment widths, the gate line and all seven grips easing
   along behind the pointer: the bar was reporting where the weights had been a
   quarter of a second ago, which on a control whose whole job is to show the
   trade you are making is the reading you are watching for. The bar and the
   rows are siblings, so the flag is on .cb-weights and the guard reaches both
   it and the tracks after it. The grip's own flag stays: it sets it on itself
   during a pointer capture, which is the one drag that does not go through a
   slider at all. */
.cb-weights:is([data-dragging], :has(.cb-weights-grip[data-dragging])) *,
.cb-weights:is([data-dragging], :has(.cb-weights-grip[data-dragging])) ~ .cb-set * {
  transition: none;
}

/* ---- The rows underneath ----
   Still here, because a trade only moves share sideways: raising one category
   without deciding on the spot who pays for it needs its own track.

   Their end labels are not. Eight copies of "0 %" and "50 %" down one panel is
   fifty-six characters of ruler saying what the ticks and the bar already say,
   and it was most of what made the old screen look like a form. */
/* Each row wears its category's colour, on the key beside the name and on the
   filled part of its own track. Without it the colours are a language spoken
   in one place on the screen and nowhere else, and the row you are editing has
   nothing in common with the share you are watching move. */
.cb-weights ~ .cb-set .cb-set-name {
  display: flex;
  align-items: center;
  gap: var(--cb-space-lg);
}
.cb-weights ~ .cb-set .cb-set-name::before {
  content: "";
  flex: none;
  inline-size: var(--cb-space-md);
  block-size: var(--cb-space-md);
  border-radius: var(--cb-radius-full);
  background: var(--tint);
}
.cb-weights ~ .cb-set .cb-slider {
  background: linear-gradient(
    to right,
    var(--tint) 0 var(--cbc-at),
    color-mix(in srgb, var(--cbc-ink) 14%, transparent) var(--cbc-at)
  );
}
.cb-weights ~ .cb-set .cb-slider::-webkit-slider-thumb {
  border-color: var(--tint);
}
.cb-weights ~ .cb-set .cb-slider::-moz-range-thumb {
  border-color: var(--tint);
}
/* No scale on these eight. Every one of them runs nought to fifty, so the
   scale is the same three numbers printed eight times, which is the clutter
   the scale was introduced to remove rather than to relocate. The ceiling
   enforces itself, the value is at the end of the row, and the bar above is
   the picture of the proportions. Scanning keeps its scales, where every
   slider has a different range and the numbers say something. */
.cb-weights ~ .cb-set .cb-slider-scale {
  display: none;
}
.cb-weights ~ .cb-set {
  transition: background var(--cb-motion-fast) var(--cbc-spring);
}
.cb-weights ~ .cb-set:hover,
.cb-weights ~ .cb-set[data-aimed="true"] {
  background: var(--cbc-hover);
}

/* A whole-row remark under a setting, e.g. what the live switch means. */
/* Renamed off .cb-note, which console-charts.css uses for something else
   entirely: an annotation on a chart, with a head, a score and a foot, and
   `display: grid` to lay them out. Charts loads first, panels never set a
   display of its own, so a settings remark inherited the grid and broke its one
   sentence into a line per element. The link in the middle of it sat on a line
   by itself with the comma after it starting the next. */
.cb-remark {
  padding: var(--cb-space-xl) var(--cb-space-3xl);
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-type-sm-line-height);
  color: var(--cbc-ink-faint);
  background: var(--cbc-sunken);
}
.cb-remark[data-tone="warn"] {
  color: var(--cbc-ink-muted);
  background: var(--cbc-accent-wash);
}

/* ---- Controls sized to what they hold ---- */
/* The third box on these screens, and it has to be the same box as the other
   two: a number field, a text field and a menu sitting in one column at three
   different heights is three controls that look like they came from three
   applications. */
.cb-num-wrap {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-md);
  padding-inline: var(--cb-space-xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-xl);
  background: var(--cbc-surface);
  transition: border-color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-num-wrap:hover {
  border-color: var(--cbc-accent);
}
.cb-num-wrap:focus-within {
  border-color: var(--cbc-accent);
}
/* Fixed so a column of settings has a column of boxes, not a ragged edge. */
.cb-set-control > .cb-num-wrap {
  width: var(--cb-space-11xl);
}
.cb-num {
  width: 8ch; /* about as wide as the longest value any of these holds */
  padding: var(--cb-space-lg) 0;
  border: 0;
  background: none;
  font-size: var(--cb-type-sm-size);
  font-variant-numeric: tabular-nums;
}
.cb-set-control > .cb-num-wrap > .cb-num-unit {
  margin-inline-start: auto;
}
.cb-num:focus {
  outline: none;
}
.cb-num-unit {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  white-space: nowrap;
}

/* The native select arrow sat about four pixels off the right edge, touching
   the border on every menu in the console. Drawing our own puts it where it
   belongs and makes it the same mark in both themes.

   currentColor in the data URI is not supported, so the chevron is a mask over
   a background colour instead: one shape, tinted by the text colour. */
.cb-pick,
.cb-select {
  appearance: none;
  /* Fills whatever it is given. A capped width here was clipping option text
     mid-word ("None, cheapes", "Provider defaul"): the control was narrower
     than its own shortest label, on a screen where the label is the whole
     point. Sizing is the column's job now, not the control's. */
  width: 100%;
  min-width: 0;
  text-overflow: ellipsis;
  /* The same box as .cb-input, to the pixel. A menu and a text field sitting in
     the same column at different heights and different corner radii is two
     controls that look like they came from two applications, and this screen
     puts them side by side constantly. */
  padding: var(--cb-space-lg) var(--cb-space-5xl) var(--cb-space-lg) var(--cb-space-xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-xl);
  background-color: var(--cbc-surface);
  background-image: none;
  font-size: var(--cb-type-sm-size);
  transition: border-color var(--cb-motion-fast) var(--cbc-spring);
}
/* The chevron itself, drawn as a rotated corner rather than an image, so it
   inherits the text colour and needs no asset. */
.cb-pick-wrap {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 0;
}
.cb-pick-wrap::after {
  position: absolute;
  inset-inline-end: var(--cb-space-lg);
  inset-block-start: 50%;
  translate: 0 -50%;
  color: var(--cbc-ink-faint);
  pointer-events: none;
}
.cb-pick-wrap > .cb-pick,
.cb-pick-wrap > .cb-select {
  width: 100%;
}
.cb-pick:hover:not(:disabled) {
  border-color: var(--cbc-accent);
}
.cb-pick:disabled {
  background: var(--cbc-sunken);
  color: var(--cbc-ink-faint);
}

.cb-hint {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  white-space: nowrap;
}
.cb-hint[data-tone="warn"] {
  color: var(--cbc-poor-text);
}

.cb-toggle-line {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-lg);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  cursor: pointer;
}
/* A switch that does not apply to the model chosen. Turned off in place rather
   than taken out of the grid (rule 15), so it still hovers and still carries
   the sentence saying why it will not move. Faded rather than removed: the row
   below it must not move because a menu above it changed. */
.cb-toggle-line[aria-disabled="true"] {
  opacity: 0.4;
  cursor: not-allowed;
}

/* ---- One stage on the routing screen ----
   Two columns: what the call is, then how it is answered. Fixing the first
   column lines every provider and model menu up down the page, so the six
   routes read as one table rather than six paragraphs. */
.cb-route-row {
  display: grid;
  grid-template-columns: var(--cb-width-xxs) minmax(0, 1fr);
  gap: var(--cb-space-3xl);
  align-items: start;
  padding: var(--cb-space-2xl) var(--cb-space-3xl);
}
.cb-route-body {
  display: grid;
  gap: var(--cb-space-lg);
}
.cb-route-foot {
  display: flex;
  align-items: center;
  gap: var(--cb-space-2xl);
  flex-wrap: wrap;
}
/* Two columns under two headings. The grid is what makes six routes read as
   one table: the model menus line up, the thinking menus line up, and a value
   can be compared down the page instead of hunted for across it. */
.cb-route {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  align-items: start;
  gap: var(--cb-space-xl);
}
.cb-route-cell {
  display: grid;
  gap: var(--cb-space-sm);
  min-width: 0;
}
/* The switch, when it appears, belongs under the model it qualifies. */
.cb-route > .cb-toggle-line {
  grid-column: 1;
  display: flex;
  align-items: center;
}
.cb-route-price {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  font-variant-numeric: tabular-nums;
}
.cb-route-heads {
  padding-block: var(--cb-space-lg);
  background: var(--cbc-sunken);
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--cb-tracking-xxs);
  color: var(--cbc-ink-faint);
}
/* The indent says it belongs to the row above. It used to be a rule down the
   left as well, which is a pattern this console does not use (docs/10). */
.cb-route-backup {
  padding-inline-start: var(--cb-space-3xl);
}

/* ---- Expandable list rows (the model catalogue, the service catalogue) ---- */
.cb-expand > summary {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xl);
  padding: var(--cb-space-xl) var(--cb-space-3xl);
  cursor: pointer;
  list-style: none;
}
.cb-expand > summary::-webkit-details-marker {
  display: none;
}
.cb-expand > summary::before {
  color: var(--cbc-ink-faint);
}
.cb-expand[open] > summary::before {
  transform: rotate(180deg);
}
.cb-expand > summary:hover {
  background: var(--cbc-sunken);
}
.cb-expand[open] > summary {
  background: var(--cbc-sunken);
}
.cb-expand-title {
  font-size: var(--cb-type-sm-size);
  font-weight: var(--cb-font-weight-medium);
  margin-inline-end: auto;
}
/* The name and the chips that qualify it, kept together on the left. */
.cb-expand-lead {
  display: flex;
  align-items: center;
  gap: var(--cb-space-lg);
  min-width: 0;
  margin-inline-end: auto;
}
.cb-expand-lead > .cb-expand-title {
  margin-inline-end: 0;
}
/* Where this one sits in the order the engine will actually use. The ranking
   menu decided that and showed it nowhere. */
.cb-rank {
  flex: none;
  inline-size: var(--cb-space-3xl);
  text-align: center;
  font-size: var(--cb-type-xs-size);
  font-variant-numeric: tabular-nums;
  color: var(--cbc-ink-faint);
}
/* Fixed columns, so a list of models reads down as well as across. */
.cb-expand-meta {
  flex: none;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cb-expand-meta[data-col="provider"] {
  width: var(--cb-space-9xl);
}
.cb-expand-meta[data-col="tier"] {
  width: var(--cb-space-10xl);
}
.cb-expand-meta[data-col="does"] {
  width: var(--cb-space-9xl);
  overflow: visible;
}
.cb-expand-meta[data-col="price"] {
  width: var(--cb-space-11xl);
  text-align: end;
}
.cb-expand-meta[data-tone="warn"] {
  color: var(--cbc-poor-text);
}
.cb-expand-body {
  border-top: 1px solid var(--cbc-line);
}
.cb-expand-body > .cb-set + .cb-set {
  border-top: 1px solid var(--cbc-line);
}
/* ---- A band over a group of rows ----
   The same job the provider band does in the model table, for lists that are
   not tables: says what the rows under it have in common, and how many. */
.cb-band {
  display: flex;
  align-items: baseline;
  gap: var(--cb-space-lg);
  padding: var(--cb-space-lg) var(--cb-space-3xl);
  background: var(--cbc-sunken);
  border-block: 1px solid var(--cbc-line);
}
.cb-band-name {
  font-size: var(--cb-type-sm-size);
  font-weight: var(--cb-font-weight-semibold);
  font-family: var(--cb-font-mono);
}
.cb-band-count {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}

/* ---- Writing the next prompt version ----
   Folded away until it is asked for. It is a text area sixteen rows tall, and
   left open at the foot of the screen it meant every visit scrolled past a
   form nobody came to fill in. */
.cb-newver {
  display: grid;
  gap: var(--cb-space-xl);
  padding: var(--cb-space-2xl) var(--cb-space-3xl);
  border-bottom: 1px solid var(--cbc-line);
  background: var(--cbc-sunken);
  animation: cbc-emerge var(--cb-motion-base) var(--cbc-spring) backwards;
}
.cb-newver-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) max-content;
  align-items: end;
  gap: var(--cb-space-xl);
}
.cb-newver .cb-textarea {
  width: 100%;
}

/* ---- Editing one catalogue entry ----
   A spec sheet, laid out like one.

   Two things made this tall. Every field carried a sentence of prose beside it,
   and every label sat on top of its control instead of next to it, which
   doubles the height of a field for no information at all. Eleven doubled
   fields is a screen and a half of scrolling for a model that fits in a
   paragraph.

   So a line is the height of its control and no more, the prose is on the
   labels, and the clusters sit two to a row: what it is on the left, what it
   costs on the right, what it can do across the foot. */
.cb-edit {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--cb-space-3xl) var(--cb-space-6xl);
  padding: var(--cb-space-2xl) var(--cb-space-3xl) var(--cb-space-3xl);
}
/* One column of clusters, for a record whose fields need the width of the row. */
.cb-edit[data-cols="1"] {
  grid-template-columns: minmax(0, 1fr);
}
.cb-spec {
  display: grid;
  align-content: start;
  gap: var(--cb-space-sm);
  min-width: 0;
}
/* A cluster wide enough to hold two lines abreast. Five short fields in one
   column is a ladder; the same five two-up is a block. */
.cb-spec[data-cols="2"] {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--cb-space-sm) var(--cb-space-6xl);
}
.cb-spec[data-cols="2"] > .cb-spec-cap,
.cb-spec[data-cols="2"] > .cb-spec-note,
.cb-spec-line[data-span="all"] {
  grid-column: 1 / -1;
}
.cb-spec[data-span="all"] {
  grid-column: 1 / -1;
  padding-block-start: var(--cb-space-xl);
  border-top: 1px solid var(--cbc-line);
}
.cb-spec-cap {
  margin-block-end: var(--cb-space-xs);
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-semibold);
  letter-spacing: var(--cb-tracking-xxs);
  text-transform: uppercase;
  color: var(--cbc-ink-faint);
}
.cb-spec-note {
  margin-block-start: calc(var(--cb-space-sm) * -1);
  margin-block-end: var(--cb-space-sm);
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-type-sm-line-height);
  color: var(--cbc-ink-muted);
}

/* Label beside control, on one line, with the label column fixed so a cluster
   reads as a column of values rather than as a ragged list of pairs. */
.cb-spec-line {
  display: grid;
  grid-template-columns: var(--cb-space-10xl) minmax(0, 1fr);
  align-items: center;
  gap: var(--cb-space-lg);
}
.cb-spec-label {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-muted);
}
.cb-spec-label > label {
  cursor: pointer;
}
.cb-spec-line > .cb-input,
.cb-spec-line > .cb-pick-wrap,
.cb-spec-line > .cb-num-wrap {
  width: 100%;
}
.cb-spec-line > .cb-num-wrap > .cb-num {
  width: 100%;
}
/* A price is four characters. A box the width of the column it happens to sit
   in says the value might be long, and it never is. */
.cb-spec-line[data-size="narrow"] > .cb-num-wrap {
  width: var(--cb-space-11xl);
}

/* A switch is a statement about the model, so it reads as one: the words, then
   the switch, kept together rather than pushed to opposite ends of a column. */
.cb-spec-flip {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-lg);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-muted);
  cursor: pointer;
}
.cb-spec-flow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cb-space-lg) var(--cb-space-5xl);
  margin-block-end: var(--cb-space-sm);
}

.cb-expand-meta[data-col="id"] {
  width: var(--cb-space-11xl);
}
.cb-expand-meta[data-tone="quiet"] {
  opacity: 0.6;
}
/* The one destructive action, at the end of the sheet and at the end of the
   row. Inside .cb-edit the padding is already the card's, so this adds none. */
.cb-edit-foot {
  display: flex;
  justify-content: flex-end;
  padding-block-start: var(--cb-space-sm);
  border-top: 1px solid var(--cbc-line);
}

/* A footer, not a slab. Filling it made a dark band across the panel that read
   as a gap between two things rather than as the end of one. */
.cb-expand-actions {
  display: flex;
  justify-content: flex-end;
  padding: var(--cb-space-xl) var(--cb-space-3xl);
  border-top: 1px solid var(--cbc-line);
}

/* ---- THE tag, and it is the console's rather than this screen's ----
   `cb-tag` is on the class guard's SHARED list, so two stylesheets are allowed
   to dress it. They should not both define its BASE, and both did: this one and
   console-dossier.css, which loads afterwards and therefore won every property
   the two disagreed on. Five of the six declarations written here had never
   applied to anything, so the service catalogue's tags looked like the
   dossier's rather than like what this file asked for, and nothing said so.
   What follows is the set that was actually shipping: the dossier's values,
   plus the one line of this file's that survived because the dossier never
   named it. Nothing moves on screen; there is simply one answer now, and it no
   longer depends on the order two <link> tags happen to sit in.
   The nowrap and the anywhere look like a contradiction and are not quite one:
   nowrap refuses a break at a space, anywhere allows one mid-word when the tag
   would otherwise overflow. That pairing is what ships today, so it is kept
   rather than quietly resolved in a commit about something else. */
.cb-tag {
  padding: var(--cb-space-xs) var(--cb-space-lg);
  border-radius: var(--cb-radius-full);
  border: 1px solid var(--cbc-line);
  background: var(--cbc-sunken);
  color: var(--cbc-ink-muted);
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-type-sm-line-height);
  overflow-wrap: anywhere;
  white-space: nowrap;
}
/* ---- A tag that stands for a rank ----
   The service catalogue's weight, which decides the order of the whole list.
   The same pastels the scoring bar uses, but arranged rather than assigned:
   there they stand for eight categories that are different and not ordered, so
   the palette carries no sense of more or less. Here the order is the point, so
   the arrangement supplies it, cool for the ones held back and warm for the
   ones led with. The palette is the same; the claim is made by the sequence. */
.cb-tag[data-rank] {
  color: var(--cbc-on-tint);
}
.cb-tag[data-rank="2"] {
  background: var(--cbc-cat-techStack);
}
.cb-tag[data-rank="3"] {
  background: var(--cbc-cat-aiVisibility);
}
.cb-tag[data-rank="4"] {
  background: var(--cbc-cat-accessibility);
}
.cb-tag[data-rank="5"] {
  background: var(--cbc-cat-performance);
}
/* Nought is not the cold end of the scale, it is off it: this service is never
   ranked up at all, so it stays out of the sequence and reads as set aside. */
.cb-tag[data-rank="0"] {
  color: var(--cbc-ink-faint);
  background: none;
  border: 1px dashed var(--cbc-line-strong);
}

/* For a tag that is a caution rather than a category: a model kept only so old
   audits can be priced from it, sitting in the same table as the live ones. */
.cb-tag[data-tone="warn"] {
  color: var(--cbc-warn-text);
  background: color-mix(in srgb, var(--cbc-warn) 16%, transparent);
}

/* A small set of yes/no choices sitting together, e.g. thinking levels. */
.cb-choices {
  display: flex;
  gap: var(--cb-space-lg);
  flex-wrap: wrap;
}
.cb-choice {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-md);
  padding: var(--cb-space-sm) var(--cb-space-lg);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-full);
  font-size: var(--cb-type-xs-size);
  cursor: pointer;
}
/* Quieter than it was. The box inside now fills with accent when it is on, and
   a filled box inside a filled pill reads as the same thing said twice. */
.cb-choice:has(input:checked) {
  border-color: var(--cbc-accent);
  background: var(--cbc-accent-quiet);
}
.cb-choice input {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}
.cb-choice:has(input:checked) > span {
  color: var(--cbc-ink);
}
.cb-choice > span {
  color: var(--cbc-ink-muted);
}

.cb-input-mono {
  font-family: var(--cb-font-mono);
}
.cb-set-control > .cb-input {
  width: var(--cb-width-xs);
}

/* A weight shown as well as stated, so the balance is visible at a glance. */
.cb-bar {
  inline-size: var(--cb-width-xxs);
  block-size: var(--cb-space-md);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-line);
  overflow: hidden;
}
.cb-bar::after {
  content: "";
  display: block;
  block-size: 100%;
  inline-size: var(--cb-fill, 0%);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-accent);
  transition: inline-size var(--cb-motion-fast) var(--cbc-spring);
}

/* ---- Chips: a set of short values that are added and removed, not typed
   into one box where a stray newline silently changes the meaning. ---- */
.cb-set-stack {
  align-items: flex-start;
}
/* A control that took the whole row used to keep pushing itself to the right
   edge anyway, which left its own label stranded above a right-aligned column
   of chips. A row that has claimed the full width reads left to right like
   everything else on the page. */
.cb-stack {
  display: grid;
  gap: var(--cb-space-lg);
  justify-items: start;
  margin-top: var(--cb-space-lg);
}
.cb-stack .cb-input {
  width: var(--cb-width-xs);
  max-width: 100%;
}

/* What a scoring preview answered, under the button that asked for it
   (docs/22 §2.2). A grid so the gap belongs to the container: the block is
   built line by line from whichever facts apply, so a caveat that only appears
   on some runs must not carry its own margin and space itself differently.
   Tighter than .cb-stack, which is spaced for controls rather than for a
   handful of sentences that read as one answer. */
.cb-preview-out {
  display: grid;
  gap: var(--cb-space-xs);
  margin-top: var(--cb-space-md);
}
/* Nothing asked yet, and no gap held open for an answer that is not there. */
.cb-preview-out:empty {
  display: none;
}

/* One settings change waiting for a second pair of eyes (docs/22 §9).
   Separated rather than boxed: the panel around them is already a frame, and a
   border on each row would put three frames inside one. The rule goes on the
   adjacent sibling so the first row has no line above it and the last has none
   below, which is what a separator means and what a plain border-bottom on
   every row gets wrong. */
.cb-waiting-row {
  display: grid;
  gap: var(--cb-space-xs);
  padding: var(--cb-space-md) 0;
}
.cb-waiting-row + .cb-waiting-row {
  border-top: 1px solid var(--cbc-line);
}

/* ---- Chips: a set of short values, read rather than typed ----
   Contacts on a business, the plugins found in a stack, the blocks a page is
   made of. Not to be confused with the token field below, which is a control:
   these are output. */
.cb-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cb-space-md);
}
.cb-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-md);
  padding: var(--cb-space-xs) var(--cb-space-md) var(--cb-space-xs) var(--cb-space-lg);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-sunken);
  font-size: var(--cb-type-xs-size);
  font-family: var(--cb-font-mono);
}
.cb-chip-x {
  border: 0;
  padding: 0 var(--cb-space-xs);
  border-radius: var(--cb-radius-full);
  background: none;
  color: var(--cbc-ink-faint);
  cursor: pointer;
  line-height: var(--cb-type-sm-line-height);
}
.cb-chip-x:hover {
  color: var(--cbc-poor-text);
}

/* ---- A field made of what you have put in it ----
   The self-scan skip list. The old shape was a sentence, a gap, and a text box
   that produced chips somewhere else on the row: three elements for one idea,
   and no way to tell by looking that the box and the chips were related.

   Here the chips are inside the box with the caret. It behaves like one field:
   it takes the focus ring as a whole, clicking the empty part starts typing,
   and it grows down the page as the list grows. */
.cb-tokens {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cb-space-md);
  inline-size: 100%;
  align-content: flex-start;
  /* As tall as what is in it. It carried a fixed floor of eighty pixels, so an
     empty list drew a large empty box for a twenty-character domain name. */
  padding: var(--cb-space-md) var(--cb-space-lg);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-xl);
  background: var(--cbc-sunken);
  cursor: text;
  transition:
    border-color var(--cb-motion-fast) var(--cbc-spring),
    background var(--cb-motion-fast) var(--cbc-spring);
}
.cb-tokens:hover {
  border-color: var(--cbc-line-strong);
}
.cb-tokens:focus-within {
  border-color: var(--cbc-accent);
  background: var(--cbc-surface);
}
/* Turned something down. A shake would be the obvious move and the wrong one:
   the reason is written underneath, and a field that jumps around draws the eye
   away from the sentence explaining itself. It flushes instead. */
.cb-tokens[data-refused] {
  border-color: var(--cbc-accent);
  background: var(--cbc-accent-quiet);
}
.cb-tokens-set {
  display: contents;
}
.cb-tokens-entry {
  flex: 1 1 var(--cb-width-xxs);
  min-inline-size: var(--cb-space-11xl);
  padding: var(--cb-space-sm) 0;
  border: 0;
  background: none;
  font-size: var(--cb-type-sm-size);
  font-family: var(--cb-font-mono);
}
.cb-tokens-entry:focus {
  outline: none;
}
.cb-tokens-entry::placeholder {
  font-family: var(--cb-font-family);
  color: var(--cbc-ink-faint);
}

.cb-token {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-md);
  padding: var(--cb-space-xs) var(--cb-space-xs) var(--cb-space-xs) var(--cb-space-lg);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-surface);
  font-size: var(--cb-type-xs-size);
  font-family: var(--cb-font-mono);
  transition: border-color var(--cb-motion-fast) var(--cbc-spring);
}
/* Only the chip that is actually new, and only the first time it is drawn.
   The entrance was on .cb-token itself, which meant every chip in the field ran
   it whenever any of them changed: taking one domain off re-popped the other
   nine, and pasting a column popped twenty at once. The field marks what it has
   just added (owned-domains.ts) and nothing else moves.

   cbc-emerge rather than cbc-pop: a chip is something small inside a card that
   has already arrived, not a popover growing out of the button beneath it. */
.cb-token[data-fresh] {
  animation: cbc-emerge var(--cb-motion-base) var(--cbc-spring) backwards;
  animation-delay: var(--cbc-step);
}
.cb-token:hover {
  border-color: var(--cbc-line-strong);
}
.cb-token > .cb-icon {
  inline-size: var(--cb-space-lg);
  block-size: var(--cb-space-lg);
  color: var(--cbc-ink-faint);
}
.cb-token-name {
  white-space: nowrap;
}
/* The way off the list, which stays quiet until it is wanted and then reads as
   the one destructive thing in the field. */
.cb-token-x {
  display: grid;
  place-items: center;
  inline-size: var(--cb-space-2xl);
  block-size: var(--cb-space-2xl);
  padding: 0;
  border: 0;
  border-radius: var(--cb-radius-full);
  background: none;
  color: var(--cbc-ink-faint);
  cursor: pointer;
  transition:
    color var(--cb-motion-fast) var(--cbc-spring),
    background var(--cb-motion-fast) var(--cbc-spring);
}
.cb-token-x .cb-icon {
  inline-size: var(--cb-space-lg);
  block-size: var(--cb-space-lg);
}
.cb-token-x:hover {
  color: var(--cbc-on-accent);
  background: var(--cbc-accent);
}
/* What the list adds up to, in the card's own header. It had a line to itself
   under a label under a heading, which was a third grey sentence introducing
   one text box. The field cannot say it inside itself either: a message shaped
   like a chip is a chip that will not come off. */
.cb-tokens-count {
  align-self: center;
  font-size: var(--cb-type-xs-size);
  font-variant-numeric: tabular-nums;
  color: var(--cbc-ink-faint);
}

/* ---- Trigger rules, built rather than typed ---- */
.cb-stack-wide {
  justify-items: stretch;
  width: 100%;
}
.cb-stack-wide .cb-btn {
  justify-self: start;
}
/* A row that took the full width and then held a 384px box was the worst of
   both: the space was claimed and not used, and a web address still truncated
   inside it. */
.cb-stack-wide .cb-input {
  width: 100%;
}
.cb-rules {
  display: grid;
  gap: var(--cb-space-md);
}
.cb-rule {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--cb-space-md);
  padding: var(--cb-space-md);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-xl);
  background: var(--cbc-sunken);
}
.cb-rule .cb-pick-wrap[data-role="signal"] {
  width: var(--cb-width-xxs);
}
.cb-rule .cb-pick-wrap[data-role="operator"] {
  width: var(--cb-space-11xl);
}
.cb-rule .cb-pick-wrap[data-role="value"] {
  width: var(--cb-space-9xl);
}
/* Fixed, so three rules with three different units still line up. */
.cb-rule .cb-num-wrap {
  width: var(--cb-space-11xl);
  background: var(--cbc-surface);
}
.cb-rule .cb-num-wrap .cb-num-unit {
  margin-inline-start: auto;
}
.cb-rule-actions {
  margin-inline-start: auto;
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
}
.cb-rule-join {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  flex-basis: 100%;
}
.cb-rule-raw {
  flex: 1 1 var(--cb-width-xxs);
  display: grid;
  gap: var(--cb-space-xs);
}

/* 768px mirrors tokens.breakpoint.md */
/* 1024px mirrors tokens.breakpoint.lg */
@media (max-width: 1024px) {
  .cb-route-row {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--cb-space-lg);
  }
}
/* 768px mirrors tokens.breakpoint.md */
@media (max-width: 768px) {
  .cb-set {
    flex-wrap: wrap;
  }
  .cb-set-control {
    margin-inline-start: 0;
    justify-content: flex-start;
  }
  .cb-route-backup {
    border-inline-start: 0;
    padding-inline-start: 0;
  }
}

/* ---- Sliders ----
   For anything with a real floor and ceiling. A track states the range, makes
   an out-of-range value impossible, and lets somebody feel where a setting sits
   between the extremes, none of which a text box does. The number stays
   visible, because "somewhere around there" is not a setting. */
/* A slider now states its range and marks its landmarks, so a value can be
   landed on rather than dragged towards. Grid rather than a row: the track and
   the readout share the top line, and the ends sit under the track they
   describe. */
.cb-slider-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  /* No row gap: the marks end at the foot of the track and the figures start
     at the top of the row under it, which is the gap. */
  gap: 0 var(--cb-space-xl);
  width: 100%;
}
/* ---- Where a value sits on a track ----
   A handle does not travel the whole width. Its centre starts half a handle in
   and stops half a handle short, so a mark drawn at a plain percentage of the
   track agrees with the handle at the midpoint and nowhere else: the minimum
   tick was drawn two pixels off the left end of the bar and the maximum two
   pixels past the right of it, and every value between was out by up to ten.

   So the position of anything on a track is this one expression, over a
   fraction the script supplies. Said once here rather than in four places. */
:root {
  --cbc-thumb: var(--cb-space-2xl);
}
/* Declared on the elements that carry --p, never on their wrapper: a custom
   property substitutes its var()s where it is declared, so a wrapper would
   compute this once against a fraction it does not have and hand every child
   the same answer. */
.cb-slider,
.cb-slider-scale > span {
  --cbc-at: calc(var(--cbc-thumb) / 2 + (100% - var(--cbc-thumb)) * var(--p, 0));
}

/* As tall as the handle, not as tall as the bar. A range input's box is the
   height of its bar and the handle overflows it, so anything measured from the
   track's own edges is measured from the wrong place. */
.cb-slider-track {
  position: relative;
  display: flex;
  align-items: center;
  block-size: var(--cbc-thumb);
}
/* The browser gives a range input a two-pixel margin of its own, which put the
   input and the scale drawn under it on different edges. */
.cb-slider-track > .cb-slider {
  width: 100%;
  margin: 0;
}

/* ---- The scale ----
   A few figures under the track, each sitting where the handle would sit on
   that value.

   Bare figures. There used to be a floor and a ceiling written out under the
   track as well as the value at the end of the row, each carrying the unit:
   "1 page", "10 pages", "5 pages", one control, one word, three times. The unit
   belongs to the setting rather than to every number in it, so it is said once
   by the value and these are numerals. */
.cb-slider-scale {
  grid-column: 1;
  position: relative;
  /* Clear of the track above them. */
  padding-block-start: var(--cb-space-xs);
  block-size: var(--cb-type-xs-line-height);
  font-size: var(--cb-type-xs-size);
  font-variant-numeric: tabular-nums;
  color: var(--cbc-ink-faint);
}
.cb-slider-scale > span {
  position: absolute;
  inset-inline-start: var(--cbc-at);
  translate: -50% 0;
  white-space: nowrap;
}
.cb-slider-scale > span:first-child {
  translate: 0 0;
}
.cb-slider-scale > span:last-child {
  translate: -100% 0;
}
.cb-slider {
  appearance: none;
  flex: 1 1 auto;
  min-width: 0;
  block-size: var(--cb-space-md);
  border-radius: var(--cb-radius-full);
  /* Two hard stops, which paints two solid blocks: the filled part of the
     track and the rest of it. A gradient in syntax only, and the only way to
     colour half a range input without a second element behind it. */
  background: linear-gradient(
    to right,
    var(--cbc-accent) 0 var(--cbc-at),
    color-mix(in srgb, var(--cbc-ink) 14%, transparent) var(--cbc-at)
  );
  cursor: pointer;
}
/* ---- The thumb, twice ----
   A vendor pseudo-element cannot share a rule with another one: a browser that
   does not know a selector in a list drops the WHOLE list, so pairing the
   webkit and moz thumbs in one selector would leave both engines with no thumb
   at all. Two blocks saying the same thing is the only way to write this, and
   the hover below is why it matters: the webkit thumb had one and the moz thumb
   did not, so in Firefox every slider on the settings screens and both band
   handles were inert under the pointer.

   And the answer is a halo rather than a scale. A control that grows under the
   pointer moves its own edges out from under the cursor, which is a hover state
   that changes what you are pointing at while you point at it; a ring painted
   around it says the same "this is live" without the target moving. It also
   costs nothing to duplicate, where a transform has to be repeated on the
   active state to stop the thumb shrinking mid-drag. */
.cb-slider::-webkit-slider-thumb {
  appearance: none;
  inline-size: var(--cb-space-2xl);
  block-size: var(--cb-space-2xl);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-surface);
  border: 2px solid var(--cbc-accent);
  cursor: grab;
  transition: box-shadow var(--cb-motion-fast) var(--cbc-spring);
}
.cb-slider::-moz-range-thumb {
  inline-size: var(--cb-space-2xl);
  block-size: var(--cb-space-2xl);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-surface);
  border: 2px solid var(--cbc-accent);
  cursor: grab;
  transition: box-shadow var(--cb-motion-fast) var(--cbc-spring);
}
.cb-slider:hover::-webkit-slider-thumb {
  box-shadow: 0 0 0 var(--cb-space-xs) color-mix(in srgb, var(--cbc-accent) 20%, transparent);
}
.cb-slider:hover::-moz-range-thumb {
  box-shadow: 0 0 0 var(--cb-space-xs) color-mix(in srgb, var(--cbc-accent) 20%, transparent);
}
.cb-slider-value {
  flex: none;
  inline-size: var(--cb-space-9xl);
  text-align: end;
  font-size: var(--cb-type-sm-size);
  font-variant-numeric: tabular-nums;
  color: var(--cbc-ink);
  font-weight: var(--cb-font-weight-medium);
}

/* ---- Two handles, one track ----
   Score bands bound each other, so they are one control. Two separate sliders
   could be set to cross, and bands where amber sits above green make every
   score in the product meaningless. The handles clamp instead. */
.cb-range-wrap {
  display: grid;
  gap: var(--cb-space-lg);
  width: 100%;
}
.cb-range-track {
  position: relative;
  block-size: var(--cb-space-2xl);
  display: flex;
  align-items: center;
}
/* The three bands painted under the handles, so the setting shows its own
   result rather than describing it. */
.cb-range-fill {
  position: absolute;
  inset-inline: 0;
  block-size: var(--cb-space-md);
  border-radius: var(--cb-radius-full);
  /* Both stops sit where the handles actually are rather than at a raw
     percentage of the track, for the reason given at --cbc-at above. */
  --cbc-band-low: calc(var(--cbc-thumb) / 2 + (100% - var(--cbc-thumb)) * var(--from, 0.5));
  --cbc-band-high: calc(var(--cbc-thumb) / 2 + (100% - var(--cbc-thumb)) * var(--to, 0.8));
  background: linear-gradient(
    to right,
    var(--cbc-poor) 0 var(--cbc-band-low),
    var(--cbc-warn) var(--cbc-band-low) var(--cbc-band-high),
    var(--cbc-good) var(--cbc-band-high) 100%
  );
}
.cb-range-handle {
  position: absolute;
  inset-inline: 0;
  appearance: none;
  block-size: var(--cb-space-2xl);
  background: none;
  margin: 0;
  /* Stacked inputs would trap every click on the top one, leaving the lower
     handle unreachable by mouse. Only the thumbs take pointer events. */
  pointer-events: none;
}
/* The same two blocks, for the same reason, and the same halo: see the note on
   .cb-slider's thumb above. The hover is hung on the input rather than on the
   thumb, so only the handle the pointer is actually over answers. */
.cb-range-handle::-webkit-slider-thumb {
  appearance: none;
  pointer-events: auto;
  inline-size: var(--cb-space-2xl);
  block-size: var(--cb-space-2xl);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-surface);
  border: 2px solid var(--cbc-ink);
  cursor: grab;
  transition: box-shadow var(--cb-motion-fast) var(--cbc-spring);
}
.cb-range-handle::-moz-range-thumb {
  pointer-events: auto;
  inline-size: var(--cb-space-2xl);
  block-size: var(--cb-space-2xl);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-surface);
  border: 2px solid var(--cbc-ink);
  cursor: grab;
  transition: box-shadow var(--cb-motion-fast) var(--cbc-spring);
}
.cb-range-handle:hover::-webkit-slider-thumb {
  box-shadow: 0 0 0 var(--cb-space-xs) color-mix(in srgb, var(--cbc-ink) 18%, transparent);
}
.cb-range-handle:hover::-moz-range-thumb {
  box-shadow: 0 0 0 var(--cb-space-xs) color-mix(in srgb, var(--cbc-ink) 18%, transparent);
}
.cb-range-handle:focus-visible {
  outline-offset: var(--cb-space-xs);
}
.cb-range-readout {
  display: flex;
  justify-content: space-between;
  gap: var(--cb-space-lg);
  font-size: var(--cb-type-xs-size);
  font-variant-numeric: tabular-nums;
}
.cb-range-readout > span {
  display: flex;
  align-items: center;
  gap: var(--cb-space-sm);
}
.cb-range-readout > span::before {
  content: "";
  inline-size: var(--cb-space-md);
  block-size: var(--cb-space-md);
  border-radius: var(--cb-radius-full);
}
.cb-range-readout > [data-band="poor"] {
  color: var(--cbc-poor-text);
}
.cb-range-readout > [data-band="poor"]::before {
  background: var(--cbc-poor);
}
.cb-range-readout > [data-band="warn"] {
  color: var(--cbc-warn-text);
}
.cb-range-readout > [data-band="warn"]::before {
  background: var(--cbc-warn);
}
.cb-range-readout > [data-band="good"] {
  color: var(--cbc-good-text);
}
.cb-range-readout > [data-band="good"]::before {
  background: var(--cbc-good);
}

/* ---- A large number ----
   Too big for a track, so it is typed. Presets sit beside it because almost
   nobody wants an arbitrary ceiling: they want the usual one, or a deliberate
   departure from it. */
/* ---- A number, with the three anybody actually picks under it ----
   One block the width of the control column: the field, then the presets in
   equal parts directly beneath it. It used to be a full-width box with the
   unit floating in the middle of it and three pills of different sizes ragged
   underneath, which read as three unrelated controls rather than as one
   setting with shortcuts. */
.cb-stepper {
  display: grid;
  gap: var(--cb-space-md);
  width: 100%;
}
.cb-stepper > .cb-num-wrap {
  width: 100%;
}
.cb-stepper .cb-num {
  width: 100%;
}
/* The unit at the end of the field rather than trailing the digits, so the
   boxes down the column all end on the same word in the same place. */
.cb-stepper .cb-num-unit {
  margin-inline-start: auto;
}
.cb-preset-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--cb-space-sm);
}
.cb-preset {
  padding: var(--cb-space-xs) var(--cb-space-sm);
  text-align: center;
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-full);
  background: none;
  color: var(--cbc-ink-faint);
  font-size: var(--cb-type-xs-size);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition:
    color var(--cb-motion-fast) var(--cbc-spring),
    border-color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-preset:hover {
  color: var(--cbc-ink);
  border-color: var(--cbc-line-strong);
}
/* Filled, not outlined. An accent ring around a number is how this console
   marks something wrong; the one you have chosen is not a warning. */
.cb-preset[aria-pressed="true"] {
  color: var(--cbc-ink);
  border-color: transparent;
  background: var(--cbc-hover-strong);
  font-weight: var(--cb-font-weight-medium);
}

/* 1024px mirrors tokens.breakpoint.lg */
@media (max-width: 1024px) {
  .cb-edit {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---- The door ----
   Whether the public form is taking work, drawn only when it is not. A mark,
   what happened, and the one thing there is to do about it, on one line so it
   reads as a state rather than as another panel of settings. */
.cb-door {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--cb-space-xl);
  padding: var(--cb-space-2xl) var(--cb-space-3xl);
}
.cb-door-mark {
  display: grid;
  place-items: center;
  width: var(--cb-space-6xl);
  height: var(--cb-space-6xl);
  border-radius: var(--cb-radius-full);
}
/* The tone is carried by the mark alone. A whole panel washed in orange is the
   loudest thing on a settings screen and reads as an error in the console
   rather than as a state of the product. */
/* Mixed from the tone itself rather than from two more named tokens: there is
   one wash in the token set (the accent's) and these are the only two places
   that need the same treatment for a state colour. */
.cb-door[data-tone="warn"] .cb-door-mark {
  background: color-mix(in srgb, var(--cbc-warn) 18%, transparent);
  color: var(--cbc-warn-text);
}
.cb-door[data-tone="bad"] .cb-door-mark {
  background: color-mix(in srgb, var(--cbc-poor) 18%, transparent);
  color: var(--cbc-poor-text);
}
.cb-door-said {
  display: grid;
  gap: var(--cb-space-xs);
}
.cb-door-said h2 {
  font-size: var(--cb-type-md-size);
  font-weight: var(--cb-font-weight-semibold);
}
.cb-door-said p {
  font-size: var(--cb-type-sm-size);
  color: var(--cbc-ink-muted);
}

/* ---- You, at the top of your own profile ----
   The face and the address the console has for you, above the one field that
   changes what it calls you. */
.cb-me {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xl);
  padding: var(--cb-space-2xl) var(--cb-space-3xl);
  border-bottom: 1px solid var(--cbc-line);
}
.cb-me-said {
  display: grid;
  gap: var(--cb-space-xxs);
  min-width: 0;
}
.cb-me-name {
  font-size: var(--cb-type-md-size);
  font-weight: var(--cb-font-weight-semibold);
}
.cb-me-mail {
  font-size: var(--cb-type-sm-size);
  color: var(--cbc-ink-muted);
}
/* The avatar here is the one place it is drawn at more than a rail's size. */
.cb-me .cb-avatar {
  width: var(--cb-space-7xl);
  height: var(--cb-space-7xl);
  font-size: var(--cb-type-md-size);
}

/* A read-only value in a settings row: the role, the date, the sentence
   somebody wrote on the Team screen. Right-aligned with the controls it sits
   among, so a panel of facts and a panel of settings read as one column.

   Not .cb-fact, which console-data.css already owns for the dt/dd pairs on a
   submission. Two files defining one class is one component silently wearing
   the other's, decided by load order (scripts/check-classes.ts). */
.cb-set-read {
  font-size: var(--cb-type-sm-size);
  color: var(--cbc-ink-muted);
  text-align: end;
}

/* ---- The passkeys on this account ----
   A list under the row that adds them, not a table: there are at most a
   handful, each is one device, and every column a table would have wants to be
   a sentence instead. The hint slot the settings row already has, so the rows
   sit under the label and the button rather than beside them. */
.cb-keys {
  display: grid;
  gap: var(--cb-space-md);
  margin: 0;
  padding: 0;
  list-style: none;
}
.cb-key {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr) max-content;
  align-items: center;
  gap: var(--cb-space-lg);
  padding: var(--cb-space-md) var(--cb-space-lg);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-md);
}
.cb-key-mark {
  color: var(--cbc-ink-faint);
}
.cb-key-mark.cb-icon {
  inline-size: var(--cb-space-2xl);
  block-size: var(--cb-space-2xl);
}
.cb-key-who {
  display: grid;
  gap: var(--cb-space-xxs);
  min-width: 0;
}
.cb-key-name {
  font-weight: var(--cb-font-weight-medium);
}
/* The whole story of one key in one line: whether it can leave the device,
   when it arrived, and whether it has ever been used. Wrapping rather than
   truncating, because "never used" is the half most likely to fall off the end
   and the half most worth reading. */
.cb-key-when {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}
.cb-key-acts {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xs);
}
.cb-key-rename {
  inline-size: 100%;
}

/* ---- Where this account has been signed in ----

   The map, the rows, and the two notes under them. The map is the console's
   own surface (views/session-map.ts) wearing a shorter aspect than the lead
   map's: this is a glance to see whether anything is somewhere surprising,
   not a thing to work in. */
.cb-session-body {
  display: grid;
  gap: var(--cb-space-xl);
  padding: var(--cb-space-2xl) var(--cb-space-3xl);
  border-bottom: 1px solid var(--cbc-line);
}
/* Two classes rather than one, and it is load order that makes it necessary:
   .cb-map-stage is console-dossier.css's and that file is linked after this
   one, so a single class here loses its aspect ratio to the lead map's 16/10.
   Both classes are on the element, so this is more specific and wins. */
.cb-map-stage.cb-session-map {
  /* Wider than it is tall, because sign-ins from one country are a cluster and
     the useful thing beside it is context, not detail. */
  aspect-ratio: 21 / 9;
  border-radius: var(--cb-radius-xl);
  overflow: hidden;
  border: 1px solid var(--cbc-line);
}
/* What is on the map, and what it means, in a card on the map itself.
   Bottom left, opposite the zoom controls, so the two never collide. It is a
   surface rather than loose text because it sits over photography: words
   dropped straight onto map tiles are legible over a field and invisible over
   a city. */
.cb-map-legend {
  position: absolute;
  inset-block-end: var(--cb-space-md);
  inset-inline-start: var(--cb-space-md);
  max-inline-size: var(--cb-width-xxs);
  display: grid;
  gap: var(--cb-space-sm);
  padding: var(--cb-space-lg);
  border-radius: var(--cb-radius-lg);
  background: var(--cbc-surface);
  border: 1px solid var(--cbc-line);
  box-shadow: var(--cbc-shadow);
}
.cb-map-legend-places {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--cb-space-xs);
}
.cb-map-legend-places li {
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
  min-width: 0;
}
/* The same colour the ring is drawn in, so the card and the map agree about
   which mark it is talking about. */
.cb-map-legend-dot {
  flex: none;
  inline-size: var(--cb-space-md);
  block-size: var(--cb-space-md);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-map-route);
}
.cb-map-legend-name {
  font-size: var(--cb-type-sm-size);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-map-legend-count {
  margin-inline-start: auto;
  flex: none;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  font-variant-numeric: tabular-nums;
}
.cb-map-legend-rest {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  margin: 0;
}
.cb-map-legend-note {
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-type-xs-line-height);
  color: var(--cbc-ink-muted);
  margin: 0;
  padding-block-start: var(--cb-space-sm);
  border-block-start: 1px solid var(--cbc-line);
}

.cb-session-notes {
  display: grid;
  gap: var(--cb-space-xs);
}
/* The name and its badge, on one line that cannot escape the column. The name
   gives way first: which browser it was matters less than whether this is the
   session you are reading from. */
.cb-session-who {
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
  min-width: 0;
}
.cb-session-device {
  font-weight: var(--cb-font-weight-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-session-who .cb-badge {
  flex: none;
}
/* The address in the mono face: it is a value to be compared character by
   character against something somebody half remembers, which is the one job
   the proportional face is bad at. */
.cb-session-ip {
  display: block;
  font-family: var(--cb-font-mono);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-muted);
  overflow: hidden;
  text-overflow: ellipsis;
}
.cb-session-place {
  display: block;
  font-size: var(--cb-type-sm-size);
  color: var(--cbc-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The map's controls, floating in its corner.
   Appearance comes from .cb-map-zoom and .cb-map-btn, which are the lead map's
   own (console-dossier.css): one set of buttons for both maps rather than two
   that drift apart. This class is placement and nothing else. */
.cb-session-zoom {
  position: absolute;
  inset-block-start: var(--cb-space-md);
  inset-inline-end: var(--cb-space-md);
  flex-direction: column;
  background: var(--cbc-surface);
}
/* Grab, and grabbing. A map that pans has to say so before it is touched. */
.cb-session-map .cb-map-surface {
  cursor: grab;
}
.cb-session-map[data-dragging] .cb-map-surface {
  cursor: grabbing;
}

/* The margin the database itself states, beside the place. Quiet, because it
   is a caveat on the line above rather than a second fact. */
.cb-session-margin {
  display: block;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  font-variant-numeric: tabular-nums;
}

/* ---- Kept on a submission ----
   Answers the assistant wrote and somebody decided were worth keeping. A log,
   not a field: each carries who kept it and when, which is what makes it
   readable by a colleague a fortnight later with none of the context. */
.cb-kept {
  display: grid;
  gap: var(--cb-space-lg);
}
.cb-kept-one {
  padding: var(--cb-space-lg);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-xl);
  background: var(--cbc-sunken);
}
.cb-kept-text {
  margin: 0;
  font-size: var(--cb-type-sm-size);
  line-height: var(--cb-type-base-line-height);
}
.cb-kept-by {
  margin: var(--cb-space-md) 0 0;
  display: flex;
  align-items: center;
  gap: var(--cb-space-sm);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}
.cb-kept-mark {
  color: var(--cbc-ink-faint);
}
.cb-kept-when {
  margin-inline-start: auto;
}

/* ---- A screen somebody may read and not change ----

   Most of the settings screens are an admin's machinery and the server refuses
   every write from anybody else, so the console stops offering them at all
   (nav.ts). Two are worth reading to everybody and changeable by nobody but an
   admin: the scoring weights ARE the SweetScore an editor defends to a
   prospect, and the service catalogue is the nearest thing the product has to a
   sales script. Hiding those would mean the only person who can explain a score
   is an admin.

   SO THEY ARE SHOWN, AND SAID ONCE. Not forty turned-off controls each carrying
   its own tooltip, which is an interface arguing with somebody a field at a
   time: one line at the top, and the screen beneath it made inert by
   views/settings.ts so nothing can be typed into it or tabbed through.

   The screen is not dimmed. It is there to be read, and grey type on a grey
   ground is the console making its own explanation harder to read than the
   thing it is explaining. What says "not yours to change" is the sentence and
   the fact that nothing responds, which is enough. */
.cb-readonly-note {
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
  margin: 0 0 var(--cb-space-3xl);
  padding: var(--cb-space-lg) var(--cb-space-xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-xl);
  background: var(--cbc-sunken);
  color: var(--cbc-ink-muted);
  font-size: var(--cb-type-sm-size);
  line-height: var(--cb-type-sm-line-height);
}
.cb-readonly-note .cb-icon {
  flex: none;
  inline-size: var(--cb-space-2xl);
  block-size: var(--cb-space-2xl);
  color: var(--cbc-ink-faint);
}
/* The save bar lives inside this, so an inert screen has nothing to press. It
   is hidden rather than left inert and visible: a bar that says "Save" under a
   screen nobody can save is the one control here that would still be making a
   promise. */
.cb-readonly .cb-savebar {
  display: none;
}

/* The saved-view bar above a list (docs/22 §9). A row that wraps, because the
   number of pinned views is a person's own business and a bar that scrolls
   sideways hides the one they pinned last. */
.cb-views-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cb-space-xs);
  margin-bottom: var(--cb-space-md);
}
/* Nothing pinned and nothing saved: no bar, and no gap where one would be. */
.cb-views-bar:empty {
  display: none;
}
.cb-views-more {
  color: var(--cbc-ink-muted);
}
