/* BonBonBot Console: the reports screen.
   ===================================================================
   WHY THIS FILE EXISTS. Reports is the one screen whose shape is a
   QUESTION rather than a subject: a row of chips, a bar of controls, a
   strip of totals, a plot, a table, and a paragraph saying what the
   numbers are worth. None of that vocabulary belongs to submissions,
   the board or the settings panels, and dropping it into one of them
   is exactly how .cb-field came to be a labelled control in one file
   and a padded card in another (scripts/check-classes.ts, which now
   fails the build on it).

   WHAT WOULD BE QUIETLY WRONG WITHOUT IT. Two things, and both are the
   kind that nobody reports as a bug.

   The bar would size itself from its own labels. "Today" and "Year to
   date" are eleven characters apart, so choosing one after the other
   would move every control beside it, and the thing somebody touches
   more than anything else on this screen would never be twice in the
   same place. Every slot here is a fixed width for that reason alone.

   And the qualification block would be ordinary furniture rather than
   the sentence that travels with every figure. It is the one element
   on this screen guaranteed to print (see the print block at the foot
   of console-theme.css): a total from a capped read is a floor, and a
   floor that leaves the building without saying so is a number
   somebody will quote in a meeting.

   Every colour is a --cbc-* from console-theme.css; every size is a
   --cb-* token. The documented exceptions are the ones the scale does
   not model: hairlines (1px), 100%/0, colour-mix percentages, and
   breakpoint literals in media queries, which CSS cannot read var()
   in. */

/* ---- THE SEVEN SUBJECTS ----
   .cb-views indents itself by a card's padding, because everywhere else in
   the console it sits inside one. Here the row is a child of the page's own
   flow, so that indent would stand the chips out of line with the bar
   directly underneath them. */
.cb-report-chips {
  padding: 0;
}
/* The chip carries its subject's own mark, the same one the gallery card for
   that subject carries, so the two screens are visibly about one set of seven
   things. The mark is held back until the chip is wanted, then follows the
   chip's own ink: one answer to the pointer, not two. */
.cb-report-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-md);
}
.cb-report-chip-mark {
  inline-size: 1em;
  block-size: 1em;
  color: var(--cbc-ink-faint);
  transition: color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-report-chip:hover .cb-report-chip-mark,
.cb-report-chip[aria-pressed="true"] .cb-report-chip-mark {
  color: currentColor;
}
/* A subject above this seat stays where it is, turned off, carrying the
   sentence that says why (rule 15). Filtering it out would leave a viewer
   looking at a shorter row with no way to tell a product that has five
   subjects from one that is not offering them two.

   DIMMED RATHER THAN RECOLOURED, and that is not the lazy option: .cb-view
   already draws an unselected chip in the faint ink, so a turned-off one
   painted the same colour is a turned-off one nobody can pick out of the row.
   Measured on screen before this was changed, the off chip and the six live
   ones were the same pixels. Opacity is what .cb-set[data-depends="off"] uses
   for the identical problem in console-panels.css, so this is the console's
   existing answer rather than a second one.

   The hover is restated because .cb-view brightens on hover, and a control
   that answers the pointer is a control that says it can be pressed. */
.cb-report-chip[aria-disabled="true"] {
  opacity: 0.5;
  cursor: default;
}
.cb-report-chip[aria-disabled="true"]:hover {
  color: var(--cbc-ink-faint);
  border-color: var(--cbc-line);
}

/* ---- THE QUERY BAR ----
   The most-touched thing on the screen, so the rule it lives by is that it
   HOLDS STILL. Every control sits in a fixed-width slot rather than one sized
   by its own content, for the reason argued at the top of this file.

   AND IT WRAPS RATHER THAN SCROLLING. A row of controls running off the side
   of a narrow window is a row of controls nobody can reach: the page body
   never scrolls sideways (console.css says so at the root, with `clip`), so a
   bar that overflowed would simply be cut off with no way to get at the rest.
   The results table is the one thing on this screen allowed a scroll of its
   own, inside .cb-table-wrap.

   The padding is --cbc-set-y, so the density preference moves this bar the
   same amount it moves a settings row rather than by a number of its own. */
.cb-report-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cb-space-lg);
  padding: var(--cbc-set-y) var(--cb-space-3xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-3xl);
  /* An input tray, recessed below the answer. The bar, the totals, the plot and
     the table used to share one surface, so the thing you touch read at the same
     weight as the thing you read. Depth by surface lightness, never a shadow or
     an edge rail (rule: no accent rail); the answer below stays on --cbc-surface
     as the raised plane, and this prints away cleanly. */
  background: var(--cbc-sunken);
}
/* One control and whatever belongs beside it. `flex: 0 0 auto` and a width on
   the control, never `flex: 1`: a slot that grows to fill the row is a slot
   whose width depends on how many other slots there are, which is the same
   bug wearing different trousers. min-inline-size: 0 lets a long option
   ellipse inside its slot instead of widening it. */
.cb-report-slot {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
  min-inline-size: 0;
}
.cb-report-slot > .cb-input {
  inline-size: var(--cb-space-10xl);
}

/* ---- CLUSTERS, AND THE HAIRLINES BETWEEN THEM ----
   Four groups: when the report is about, how it is cut, what is counted and
   narrowed, and the switch you turn on last. A cluster is `flex: 0 0 auto` so
   it WRAPS AS A WHOLE: left as eight loose slots the bar breaks wherever the
   window happens to end, and "Then by" lands on a different line from "Group
   by", which is the one pairing here that has to be read together.

   The divider is the same neutral hairline the totals strip uses, BETWEEN
   clusters. It is not a bar down the edge of anything, and it adds a constant
   1px, so the bar still holds still. */
.cb-report-cluster {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cb-space-lg);
}
.cb-report-div {
  flex: none;
  inline-size: 1px;
  align-self: stretch;
  background: var(--cbc-line);
}
/* Two words naming a slot, so a row of similar menus can be told apart before
   any of them is read. Its own class, never .cb-report-slot > span, which is
   the word between the two date boxes. */
.cb-report-tag {
  flex: none;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  white-space: nowrap;
}

/* ---- THE LISTBOX ----
   The bar's own dropdown (reports/listbox.ts). The wrapper is auto-width and
   holds a constant mark and micro-label; the WIDTH LIVES ON THE BUTTON, with
   min-inline-size 0 and an ellipsis, so choosing "Year to date" after "Today"
   never moves the control beside it. */
.cb-listbox-wrap {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
  min-inline-size: 0;
}
.cb-listbox-glyph {
  flex: none;
  color: var(--cbc-ink-faint);
}
.cb-listbox {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
  inline-size: var(--cb-space-11xl);
  min-inline-size: 0;
  padding: var(--cb-space-sm) var(--cb-space-lg);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-lg);
  background: var(--cbc-surface);
  color: var(--cbc-ink);
  font: inherit;
  font-size: var(--cb-type-sm-size);
  text-align: start;
  cursor: pointer;
  transition: border-color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-listbox:hover {
  border-color: var(--cbc-line-strong);
}
.cb-listbox-value {
  flex: 1 1 auto;
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The mark turns to say the list is open. Transform only: nothing moves. */
.cb-listbox-mark {
  flex: none;
  inline-size: 1em;
  block-size: 1em;
  color: var(--cbc-ink-faint);
  transition: transform var(--cb-motion-fast) var(--cbc-spring);
}
.cb-listbox[aria-expanded="true"] .cb-listbox-mark {
  transform: rotate(180deg);
}
@media (prefers-reduced-motion: reduce) {
  .cb-listbox-mark {
    transition: none;
  }
}
/* Turned off carrying its reason, never taken away (rule 15). */
.cb-listbox[aria-disabled="true"] {
  opacity: 0.5;
  cursor: default;
}
.cb-listbox[aria-disabled="true"]:hover {
  border-color: var(--cbc-line);
}

/* One row of the list. The highlight is `data-on`, which the keyboard moves
   VIRTUALLY: the focus never leaves the trigger, so the browser's own focus
   ring is not what says where you are. */
.cb-report-opt {
  overflow: hidden;
  padding: var(--cbc-row-y) var(--cb-space-md);
  border-radius: var(--cb-radius-sm);
  font-size: var(--cb-type-sm-size);
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}
.cb-report-opt[data-on] {
  background: var(--cbc-hover);
}
.cb-report-opt[aria-selected="true"] {
  font-weight: var(--cb-font-weight-medium);
}
/* A heading inside the list, drawn like the checklist's own group headings. */
.cb-report-head {
  margin: 0;
  padding: var(--cb-space-sm) var(--cb-space-md) var(--cb-space-xxs);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}

/* ---- COMPARE ----
   It was nine words and a switch, which made the least important control on
   the bar the widest thing on it. A fixed slot now, quiet until it is on: an
   accent here would be the loudest mark on a row of quiet menus. */
.cb-report-compare {
  flex: none;
  display: 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-lg);
  background: none;
  color: var(--cbc-ink-faint);
  font: inherit;
  font-size: var(--cb-type-sm-size);
  cursor: pointer;
  transition:
    border-color var(--cb-motion-fast) var(--cbc-spring),
    background var(--cb-motion-fast) var(--cbc-spring),
    color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-report-compare:hover {
  border-color: var(--cbc-line-strong);
  color: var(--cbc-ink);
}
.cb-report-compare[aria-checked="true"] {
  border-color: var(--cbc-line-strong);
  background: var(--cbc-hover);
  color: var(--cbc-ink);
}
.cb-report-compare .cb-icon,
.cb-report-compare .cb-report-tag {
  flex: none;
  color: inherit;
}
.cb-report-compare .cb-icon {
  inline-size: 1em;
  block-size: 1em;
}
/* The word between two date boxes. Real words rather than a drawn dash: two
   date fields side by side with nothing between them read as two unrelated
   settings. It never wraps and never sets the slot's width. */
.cb-report-slot > span {
  flex: none;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}

/* ---- THE FILTERS ----
   Their own line inside the bar, taking the whole of it. A filter carries a
   field, an operator and a list of values, so mixed into the row of menus it
   was the one item whose width changed as you built it, which is precisely
   what the bar exists not to do. */
.cb-report-filters {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cb-space-md);
}
.cb-report-filter {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
  max-inline-size: 100%;
  padding: var(--cb-space-xs) var(--cb-space-lg);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-full);
  /* A raised chip in the recessed tray now that the bar itself is sunken: on a
     sunken bar a sunken pill lost its edge, so the filter reads as surface. */
  background: var(--cbc-surface);
  font-size: var(--cb-type-xs-size);
  transition: border-color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-report-filter:hover {
  border-color: var(--cbc-line-strong);
}
/* The field is the part you read; the operator is grammar around it. */
.cb-report-filter > strong {
  font-weight: var(--cb-font-weight-medium);
}
.cb-report-filter > em {
  font-style: normal;
  color: var(--cbc-ink-faint);
}
/* Truncated, never wrapped. "is any of" over eleven models is otherwise a
   filter three lines tall sitting inside a bar of single-line controls. */
.cb-report-filter > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* THE AFFORDANCE ARRIVES WHEN YOU DO, and it costs no layout when it does.
   The control is in the box from the first frame at opacity 0, so revealing
   it moves nothing (rule 14). Not `display: none`, which would widen the pill
   the moment a pointer crossed it, and not `visibility`, which takes it out
   of the tab order for somebody who never uses a pointer at all. */
.cb-report-filter > button {
  flex: none;
  display: grid;
  place-items: center;
  border: 0;
  padding: 0;
  background: none;
  color: var(--cbc-ink-faint);
  opacity: 0;
  cursor: pointer;
  transition:
    opacity var(--cb-motion-fast) var(--cbc-spring),
    color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-report-filter:hover > button,
.cb-report-filter:focus-within > button {
  opacity: 1;
}
.cb-report-filter > button:hover {
  color: var(--cbc-poor-text);
}

/* ---- THE VALUE MENU ----
   Every filter is a menu and never a text box: nobody mistypes a value they
   picked from a list, and a typo in a box is a report that answers "no rows"
   rather than "no such model" (shared/src/reports.ts argues it at length).
   Which means this can be long, so it scrolls inside itself rather than
   growing past the window.

   A popover in the top layer, coming from the control that opened it, at the
   shape .cb-menu already settled on in console-charts.css. A card clips its
   overflow, so an ordinary panel opened near a card's edge is a panel with
   its corner cut off. */
.cb-report-pop,
.cb-report-pop:popover-open {
  position: fixed;
  inset: auto;
  inset-block-start: var(--y, 0);
  inset-inline-start: var(--x, 0);
  margin: 0;
  z-index: var(--cbc-z-menu);
  display: grid;
  /* minmax(0, 1fr) AND NOT THE IMPLICIT `auto`, which is the whole reason a
     model id longer than the menu used to push the count out through the
     right-hand edge. An auto track is sized by its content and then overflows
     its container rather than clipping, so the one figure in the row that says
     how many rows carry this value was the first thing off the screen. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--cb-space-xxs);
  inline-size: var(--cb-width-xs);
  max-inline-size: 100%;
  max-block-size: var(--cb-width-sm);
  overflow-y: auto;
  padding: var(--cb-space-md);
  border: 1px solid var(--cbc-line-strong);
  border-radius: var(--cb-radius-lg);
  background: var(--cbc-raised);
  box-shadow: var(--cbc-shadow);
  color: var(--cbc-ink);
  transform-origin: top left;
  --cbc-pop-from: calc(var(--cb-space-xs) * -1);
  animation: cbc-pop var(--cb-motion-fast) var(--cbc-spring) backwards;
}
/* The fallback for a browser with no popover support, as .cb-menu does it. */
.cb-report-pop:not(:popover-open):not([data-open]) {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .cb-report-pop,
  .cb-report-pop:popover-open {
    animation: none;
  }
}
/* One value in that menu, and the count is the half that earns its place: it
   is what tells "there are no Vertex rows in this range" apart from "you have
   not ticked Vertex", and in an empty table those two look identical. */
.cb-report-check {
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
  min-inline-size: 0;
  padding: var(--cbc-row-y) var(--cb-space-md);
  border-radius: var(--cb-radius-sm);
  font-size: var(--cb-type-sm-size);
  cursor: pointer;
  transition: background var(--cb-motion-fast) var(--cb-motion-ease);
}
.cb-report-check:hover {
  background: var(--cbc-hover);
}
.cb-report-check > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-report-check > small {
  flex: none;
  margin-inline-start: auto;
  font-size: var(--cb-type-xs-size);
  font-variant-numeric: tabular-nums;
  color: var(--cbc-ink-faint);
}

/* ---- THE GALLERY ----
   What the screen opens on. A menu of finished questions in the words
   somebody would ask them, because a blank builder with seven subjects and a
   hundred and thirty fields answers nothing at all until you have learned its
   vocabulary, and the people this was built for are the ones who will not.

   auto-fill rather than a column count, so the same markup reads as three
   across on a monitor and one on a laptop without a breakpoint being asked to
   decide where those are. */
.cb-report-gallery {
  display: grid;
  gap: var(--cbc-flow-gap);
}

/* ---- THE SEARCH, ABOVE EVERYTHING ----
   The screen has two readers and this is the second one's. The first visit
   presses a card; the fiftieth types, because by then the question is "where
   was that one" and eighteen equally weighted things answer that no better
   than a blank screen. It understands the words somebody uses rather than
   only the ones the reports are written in (search-words.ts).

   Recessed, like the query bar on a report: a thing you type into sits below
   the plane of the things you read, which is the depth rule this screen
   already keeps. */
.cb-report-find {
  display: flex;
  align-items: center;
  gap: var(--cb-space-lg);
  padding: var(--cb-space-lg) var(--cb-space-2xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-2xl);
  background: var(--cbc-sunken);
  transition: border-color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-report-find:focus-within {
  border-color: var(--cbc-accent);
}
.cb-report-find-mark {
  display: grid;
  place-items: center;
  flex: none;
  color: var(--cbc-ink-faint);
}
/* The box carries no border of its own: the row around it is the control, and
   a field inside a field is two edges saying one thing. */
.cb-report-find-input {
  flex: 1;
  min-inline-size: 0;
  border: 0;
  background: transparent;
  color: var(--cbc-ink);
  font: inherit;
  font-size: var(--cb-type-base-size);
  outline: none;
}
.cb-report-find-input::placeholder {
  color: var(--cbc-ink-faint);
}
/* How many there are, and how many match once somebody types. It is the one
   thing on the row that changes as they go, which is what makes the search
   feel like it is answering rather than filtering silently. */
.cb-report-find-count {
  flex: none;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  font-variant-numeric: tabular-nums;
}

/* ---- THE THREE THAT LEAD ----
   Not all eighteen are equal and a screen that pretends otherwise wastes its
   most valuable inches. One each for what it costs, whether work is coming in,
   and whether the machine is healthy (report-presets.ts marks them).

   auto-fit rather than three columns, so the row is three across on a monitor
   and one on a laptop without a breakpoint deciding where that happens. */
.cb-report-leads {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--cb-width-xxs), 1fr));
  gap: var(--cbc-flow-gap);
}
.cb-report-lead {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: var(--cb-space-lg);
  padding: var(--cb-space-3xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-3xl);
  background: var(--cbc-surface);
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
  transition:
    border-color var(--cb-motion-fast) var(--cbc-spring),
    background var(--cb-motion-fast) var(--cbc-spring);
}
/* Answers immediately and moves nothing: no lift, no scale, no shadow that
   changes the box and nudges the cards beside it (rule 14). */
.cb-report-lead:hover {
  border-color: var(--cbc-line-strong);
  background: var(--cbc-hover);
}
.cb-report-lead > strong {
  font-size: var(--cb-type-md-size);
  font-weight: var(--cb-font-weight-semibold);
  letter-spacing: -0.01em;
}
.cb-report-lead > span:not(.cb-report-well) {
  font-size: var(--cb-type-sm-size);
  line-height: var(--cb-type-sm-line-height);
  color: var(--cbc-ink-faint);
}
/* THE MARK SITS IN SOMETHING. A glyph alone above a title is a glyph somebody
   dropped there; the same glyph in a well is the card's own object. The well
   is the recessed ground the query bar uses, so this borrows a plane the
   screen already has rather than inventing a second one. */
.cb-report-well {
  display: grid;
  place-items: center;
  inline-size: var(--cb-space-6xl);
  block-size: var(--cb-space-6xl);
  border-radius: var(--cb-radius-xl);
  background: var(--cbc-sunken);
  color: var(--cbc-ink-faint);
  transition:
    background var(--cb-motion-fast) var(--cbc-spring),
    color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-report-lead:hover .cb-report-well,
.cb-report-lead:focus-visible .cb-report-well {
  background: var(--cbc-surface);
  color: var(--cbc-ink);
}

/* ---- A HEADING, AND THE RULE THAT CARRIES IT ----
   Four words of small uppercase floating over a list read as a stray label;
   the rule makes them a division of the page, which is what they are. */
.cb-report-head {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xl);
  margin: var(--cb-space-3xl) 0 var(--cb-space-lg);
  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);
}
.cb-report-head::after {
  content: "";
  flex: 1;
  block-size: 1px;
  background: var(--cbc-line);
}

/* ---- EVERYTHING ELSE IS A LIST ----
   Rows rather than a grid of equal cards, because eighteen cards of one size
   rank nothing, grow a row taller with every report added, and are slower to
   scan: the eye follows one edge down a list and hunts across a grid.

   Two columns where there is room. The list is long enough that one column on
   a monitor is a lot of scrolling for eighteen short lines. */
.cb-report-list {
  display: grid;
  /* Two columns on a monitor, one on a laptop. Measured rather than chosen: at
     the narrower track this fits three across and every blurb truncates in the
     middle of the sentence that says what the report answers, which is the one
     line on the row worth reading. A wider track buys the sentence. */
  grid-template-columns: repeat(auto-fill, minmax(var(--cb-width-sm), 1fr));
  gap: 1px;
}
.cb-report-pick {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xl);
  padding: var(--cb-space-lg);
  border: 0;
  border-radius: var(--cb-radius-xl);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
  transition: background var(--cb-motion-fast) var(--cbc-spring);
}
.cb-report-pick:hover {
  background: var(--cbc-hover);
}
.cb-report-pick-mark {
  display: grid;
  place-items: center;
  flex: none;
  color: var(--cbc-ink-faint);
  transition: color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-report-pick:hover .cb-report-pick-mark {
  color: var(--cbc-ink);
}
/* min-inline-size 0 so the blurb can ellipse instead of widening the row. */
.cb-report-pick-said {
  display: grid;
  gap: var(--cb-space-xxs);
  min-inline-size: 0;
  flex: 1;
}
.cb-report-pick-said > strong {
  font-size: var(--cb-type-sm-size);
  font-weight: var(--cb-font-weight-medium);
}
/* One line, clipped. A wrapped blurb makes one row twice the height of the
   rest and the list stops being a list. */
.cb-report-pick-said > em {
  font-style: normal;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The group, on a search result only: a ranked list has lost the headings that
   would otherwise say which part of the product a report belongs to. */
.cb-report-pick-tag {
  flex: none;
  padding: var(--cb-space-xxs) var(--cb-space-lg);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-full);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  white-space: nowrap;
}
.cb-report-none {
  margin: 0;
  padding: var(--cb-space-3xl) var(--cb-space-lg);
  font-size: var(--cb-type-sm-size);
  color: var(--cbc-ink-faint);
}

/* THE STAGGER COMES FROM WHERE A CARD SITS, never from a typed delay: --i is
   set in gallery.ts in reading order and the stylesheet turns it into time.
   --cbc-inside rather than --cbc-step, because the gallery is inside a
   container that is itself still arriving, and a queue starting the same
   instant the box does happens entirely underneath it (console-theme.css). */
.cb-report-gallery > *,
.cb-report-browse > * {
  animation: cbc-emerge var(--cb-motion-slow) var(--cbc-spring) backwards;
  animation-delay: var(--cbc-inside);
}
@media (prefers-reduced-motion: reduce) {
  .cb-report-gallery > * {
    animation: none;
  }
}

/* ---- THE TOTALS ----
   The grand total, folded over everything read rather than over the page,
   which is the distinction the coverage block underneath exists to keep
   honest. Hairline separators through a 1px gap over a lined ground, so
   twelve measures read as one instrument rather than twelve boxes. */
.cb-report-totals {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--cb-space-11xl), 1fr));
  gap: 1px;
  overflow: hidden;
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-3xl);
  background: var(--cbc-line);
}
.cb-report-total {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--cb-space-sm);
  /* A <dl> now, so its default block margin would break the 1px hairline gap
     between tiles. Zeroed here rather than fought in the grid. */
  margin: 0;
  padding: var(--cb-space-2xl) var(--cb-space-3xl);
  background: var(--cbc-surface);
}
/* The name of the reading, set as a label rather than as a sentence: small,
   spaced and upper, so the figure under it is unmistakably the thing to read
   and the two never compete. */
.cb-report-total > dt {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-medium);
  text-transform: uppercase;
  letter-spacing: var(--cb-tracking-xxs);
  color: var(--cbc-ink-faint);
}
.cb-report-total > dd {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--cb-space-md);
  margin: 0;
  /* The largest type on the screen, because these are the figures the screen
     exists to produce. Tightened, because numbers at this size set loose read
     as a heading rather than as a quantity. */
  font-size: var(--cb-type-2xl-size);
  font-weight: var(--cb-font-weight-semibold);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  line-height: var(--cb-type-2xl-line-height);
}
/* ABSENT IS NOT ZERO, and it must not be set like a quantity. A measure with
   nothing behind it drops to the body weight in the faint ink, so a month
   with no scans reads as an absence rather than as a very small number. */
.cb-report-total[data-measured="false"] > dd {
  font-size: var(--cb-type-lg-size);
  font-weight: var(--cb-font-weight-regular);
  color: var(--cbc-ink-faint);
}
/* How far a figure moved, or nothing. There is deliberately no tone when the
   catalogue holds no opinion about which direction is better: a green arrow
   on a count of scans would be the product having a view it does not have. */
.cb-report-delta {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-xxs);
  padding: 0 var(--cb-space-sm);
  border-radius: var(--cb-radius-xs);
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-medium);
  font-variant-numeric: tabular-nums;
  color: var(--cbc-ink-faint);
  cursor: help;
}
/* The lucide direction mark, sized to the chip's own small text rather than to
   the icon default, so it sits on the number's line (rule: use icons, never a
   typed arrow). */
.cb-report-delta .cb-icon {
  inline-size: 1em;
  block-size: 1em;
}
.cb-report-delta[data-tone="good"] {
  background: color-mix(in srgb, var(--cbc-good) 14%, transparent);
  color: var(--cbc-ink);
}
.cb-report-delta[data-tone="poor"] {
  background: color-mix(in srgb, var(--cbc-poor) 12%, transparent);
  color: var(--cbc-ink);
}
.cb-report-delta[data-tone="flat"] {
  background: var(--cbc-hover);
}

/* ---- WHAT THE ANSWER IS WORTH ----
   THE MOST IMPORTANT ELEMENT ON THIS SCREEN, and the reason the print block
   in console-theme.css keeps exactly one thing visible whatever else goes.

   A total from a capped read is a floor. A cost computed from fixtures is not
   a bill. A sum in Canadian dollars at a rate nobody could fetch is a sum in
   US dollars wearing the wrong symbol. Every one of those is a number that
   looks exactly like a good one, so the sentence saying which sits WITH the
   figure rather than somewhere a screenshot can crop it off.

   Quiet by default, because most runs qualify nothing worse than the zone
   they were counted in, and an alarm that fires every time is an alarm nobody
   reads. data-tone lifts it only when something was genuinely capped,
   unpriced, or served from fixtures. */
.cb-report-cover {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cb-space-md) var(--cb-space-xl);
  padding: var(--cb-space-lg) var(--cb-space-xl);
  border-radius: var(--cb-radius-xl);
  background: var(--cbc-sunken);
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-type-sm-line-height);
  color: var(--cbc-ink-faint);
}
.cb-report-cover strong {
  font-weight: var(--cb-font-weight-medium);
  color: var(--cbc-ink);
}
/* THE MARKS, AND WHY THE WHOLE BOX NO LONGER LIFTS.
   This block used to paint the entire note amber whenever anything was wrong
   with the run, which said "something here is off" and nothing about what: one
   tone for a capped read, an unfetched rate and a fixture install alike. Three
   small pills naming the three things scan faster and say more, so the tone
   moved onto them and the box went back to being quiet. */
.cb-cover-chips {
  flex: 1 0 100%;
  display: flex;
  flex-wrap: wrap;
  gap: var(--cb-space-sm);
  align-items: center;
}
/* Shaped like the delta chip, because they are the same kind of object: a small
   fact with a mark and a tone, sitting inside a line of text. */
.cb-cover-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-xxs);
  padding: 0 var(--cb-space-sm);
  border-radius: var(--cb-radius-xs);
  background: var(--cbc-hover);
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-medium);
  color: var(--cbc-ink-faint);
  cursor: help;
}
.cb-cover-chip[data-tone="warn"] {
  background: color-mix(in srgb, var(--cbc-warn) 14%, transparent);
  color: var(--cbc-ink);
}
.cb-cover-chip .cb-icon {
  inline-size: 1em;
  block-size: 1em;
}

/* ---- THE PLOT ----
   One picture of the measure somebody is looking at, over the table that
   holds all of them. It keeps its height whatever the data does: a chart tall
   when there are readings and short when there are none moves the whole table
   under the pointer every time the range changes, which is the console
   appearing to rearrange itself in answer to a menu. */
.cb-report-plot {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: var(--cb-space-2xl);
  min-block-size: calc(var(--cbc-card-row) * 2);
  padding: var(--cb-space-3xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-3xl);
  background: var(--cbc-surface);
}
/* ---- THE REPORT'S OWN NAME ----
   The page headline is the section and stays the section, so without this a
   report had no name anywhere on it: fourteen different answers all headed
   "Reports". This is the line somebody reads to know what they are looking at,
   so it is set like a headline and not like a caption, with the period under it
   in the muted ink. It sits at the top of the answer rather than in the page
   head because it re-stamps on every run, and the head's own title is animated
   letter by letter on arrival. */
.cb-report-mast {
  display: grid;
  gap: var(--cb-space-sm);
}
.cb-report-mast-name {
  margin: 0;
  font-size: var(--cb-type-2xl-size);
  line-height: var(--cb-type-2xl-line-height);
  font-weight: var(--cb-font-weight-semibold);
  letter-spacing: -0.02em;
  /* The one line worth reading first, so it takes the strongest ink on a
     screen where everything else is deliberately quiet. */
  color: var(--cbc-ink);
}
.cb-report-mast-said {
  margin: 0;
  font-size: var(--cb-type-sm-size);
  color: var(--cbc-ink-faint);
}

/* ---- WHAT THIS ANSWER IS, ABOVE THE ANSWER ----
   The count and the caveats were two loose things stacked over the widest
   element on the screen: a line of small grey text, then a box of sentences,
   then a table. One band instead, bled to the card's own edges and closed with
   a hairline, so the card reads as a titled thing rather than as a table with
   notes floating above it. It is the same recessed ground the query bar uses,
   which is the plane this screen already keeps for things that are not the
   answer itself. */
.cb-report-band {
  display: grid;
  gap: var(--cb-space-lg);
  margin: calc(var(--cb-space-3xl) * -1) calc(var(--cb-space-3xl) * -1) var(--cb-space-3xl);
  padding: var(--cb-space-2xl) var(--cb-space-3xl);
  border-block-end: 1px solid var(--cbc-line);
  background: var(--cbc-sunken);
}
/* The count is the band's own label, so it is set as one. */
.cb-report-band > .cb-count {
  margin: 0;
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-medium);
  text-transform: uppercase;
  letter-spacing: var(--cb-tracking-xxs);
  color: var(--cbc-ink-faint);
}
/* Inside the band the note has no ground of its own: two recessed boxes one
   inside the other is a border nobody asked for. */
.cb-report-band .cb-report-cover {
  padding: 0;
  border-radius: 0;
  background: none;
}

/* ---- THE READINGS ON THE AXIS ----
   A run over time can carry three at once, so the control is a row of names you
   turn on and off rather than a menu of one. The dot is the mark that reading
   is drawn as, taking the plot's own three colour roles, so the legend and the
   chart cannot disagree about which line is which. A chip that would take the
   count past three, or below one, is turned off carrying the reason (rule 15)
   rather than failing on the press. */
.cb-report-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cb-space-md);
  padding: 0;
}
.cb-report-series[data-role]::before {
  content: "";
  inline-size: var(--cb-space-md);
  block-size: var(--cb-space-md);
  border-radius: var(--cb-radius-full);
  background: currentColor;
}
.cb-report-series {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-md);
}
.cb-report-series[data-role="bar"] {
  color: var(--cbc-series-bar, var(--cbc-accent));
}
.cb-report-series[data-role="line-a"] {
  color: var(--cbc-series-a, var(--cbc-good));
}
.cb-report-series[data-role="line-b"] {
  color: var(--cbc-series-b, var(--cbc-warn));
}
.cb-report-series[aria-disabled="true"] {
  opacity: 0.5;
  cursor: default;
}

/* ---- TABLE OR MATRIX ----
   Offered only where a run has two cuts in it, which is the only time there is
   a grid to draw. It sits in the band with the count and the caveats, because
   it is a statement about what is below rather than a control on the query:
   the bar asks the question, this decides how the answer is laid out. */
.cb-report-laid {
  display: flex;
  align-items: center;
  gap: var(--cb-space-lg);
}
.cb-report-shape {
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
  padding: 0;
}
.cb-report-shape > .cb-listbox-wrap {
  margin-inline-start: var(--cb-space-md);
}

/* ---- A FIGURE WITH A CEILING, DRAWN AGAINST IT ----
   A score and a rate both have a real scale, so a bar under one is a fact
   rather than an ornament: half full means half. Everything without a ceiling
   (a cost, a count, a duration) gets nothing, because the only bar available
   for those is a share of the largest figure on the page, which moves when the
   range moves and says nothing about the number itself (row.ts argues it).

   HELD STILL, which is the one thing changed about the shared mark. A meter
   arrives empty and grows, and on the eight scores of a submission that is
   right; sixty of them in a table would set the whole grid growing on every
   run, which reads as a page loading rather than as a page being read. The
   shape and the tone are reused exactly; only the entrance is dropped. */
/* A GAUGE UNDER THE FIGURE, NOT A RULE UNDER THE CELL. At the meter's own full
   width it ran the whole column and read as an underline drawn across the row,
   with the number floating off its right end: the bar has to belong to the
   figure it measures, so it takes a fixed short width and sits under it, ending
   where the number ends. `auto` on the leading margin is what pushes it to the
   figure's own edge in a column that is right-aligned. */
.cb-report-fill {
  inline-size: var(--cb-space-8xl);
  margin-block-start: var(--cb-space-xs);
  margin-inline-start: auto;
}
.cb-report-fill .cb-meter-fill {
  animation: none;
}

/* ---- WHY THESE NUMBERS ARE WHAT THEY ARE ----
   Under the table, because a note is context for a figure somebody has already
   read. Above it, every report would open on somebody else's commentary.

   Quiet by construction: the same recessed ground the query bar uses, small
   type, and no border of its own. It is the last thing on the screen and it
   should read as an aside, not as a second answer. */
.cb-report-notes {
  display: grid;
  gap: var(--cb-space-xl);
  padding: var(--cb-space-2xl);
  border-radius: var(--cb-radius-3xl);
  background: var(--cbc-sunken);
}
.cb-report-notes-head {
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
  margin: 0;
  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);
}
.cb-report-notes-list {
  display: grid;
  gap: var(--cb-space-lg);
}
/* The note, who wrote it, and the way to take it off, on one line at a width
   that reads: a paragraph running the full width of a report is a paragraph
   nobody finishes. */
.cb-report-note {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--cb-space-xs) var(--cb-space-xl);
}
.cb-report-note-text {
  grid-column: 1;
  margin: 0;
  max-inline-size: var(--cb-width-xl);
  font-size: var(--cb-type-sm-size);
  line-height: var(--cb-type-sm-line-height);
  color: var(--cbc-ink);
}
.cb-report-note-who {
  grid-column: 1;
  margin: 0;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}
.cb-report-note > .cb-btn {
  grid-column: 2;
  grid-row: 1 / span 2;
}
.cb-report-notes-add {
  display: flex;
  gap: var(--cb-space-lg);
  align-items: center;
}
.cb-report-notes-add > .cb-input {
  flex: 1;
  min-inline-size: 0;
}

/* ---- THIS ANSWER IS KEPT, NOT LIVE ----
   The band above a frozen report, and its whole job is to be read BEFORE the
   figures underneath it. Every other table on this screen is live, so one that
   looks live and is three months old is worse than no table at all: somebody
   reads today's answer off it.

   A NOTE RATHER THAN A WARNING. Nothing has gone wrong and nobody needs to act;
   what this says is what kind of thing you are looking at. So it sits on the
   recessed ground the query bar uses rather than in an alarm colour, which this
   console spends on things that are actually wrong. */
.cb-report-frozen {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xl);
  padding: var(--cb-space-xl) var(--cb-space-2xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-2xl);
  background: var(--cbc-sunken);
  font-size: var(--cb-type-sm-size);
  color: var(--cbc-ink-muted);
}
.cb-report-frozen-mark {
  display: grid;
  place-items: center;
  flex: none;
  color: var(--cbc-ink-faint);
}
.cb-report-frozen strong {
  color: var(--cbc-ink);
  font-weight: var(--cb-font-weight-semibold);
}
/* The way to today's version of the same cut, which is the question anybody
   reading an old figure asks next. Pushed to the end so it reads as the way
   out rather than as part of the sentence. */
.cb-report-frozen > .cb-btn {
  margin-inline-start: auto;
  flex: none;
}

/* ---- WHAT A FIGURE IS MADE OF ----
   The panel a folded row opens. It borrows .cb-pop, so it is drawn in the top
   layer and positioned off the row that opened it like every other popover on
   this screen (rule 14: a popover comes from the thing it belongs to).

   Scrolls at a fixed height rather than growing: a group of twenty scans would
   otherwise be a panel taller than the window, and the point of it is to be
   glanced at beside the number it explains. */
.cb-report-behind {
  inline-size: var(--cb-width-xxs);
  max-block-size: calc(var(--cb-space-11xl) * 2);
  overflow-y: auto;
  padding: var(--cb-space-xl);
}
.cb-report-behind-head {
  margin: 0 0 var(--cb-space-lg);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}
/* An id, in the face ids are read in. It had no rule, so a document id sat in
   the body face beside the words describing it and read as prose. */
.cb-mono {
  font-family: var(--cb-font-mono);
  font-size: var(--cb-type-xs-size);
}
.cb-report-behind-list {
  display: grid;
  gap: 1px;
}
.cb-report-behind-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cb-space-lg);
  padding: var(--cb-space-md) var(--cb-space-lg);
  border: 0;
  border-radius: var(--cb-radius-lg);
  background: transparent;
  color: var(--cbc-ink-muted);
  font-size: var(--cb-type-xs-size);
  cursor: pointer;
  text-align: start;
}
.cb-report-behind-row:hover {
  background: var(--cbc-hover);
  color: var(--cbc-ink);
}

/* ---- SENDING ITSELF ----
   A switch and, once it is on, the three things that make a cadence. The rest
   is hidden rather than turned off, which is the opposite of rule 15 and right
   here: those controls do not "not apply", they do not EXIST until something
   sends itself, and a row of greyed dropdowns under an off switch is furniture
   that has to be read to be dismissed. The switch itself follows rule 15
   exactly, staying put and carrying its reason when a private report cannot
   post to a shared channel. */
.cb-report-sched {
  display: grid;
  gap: var(--cb-space-lg);
}
.cb-report-sched-toggle {
  justify-self: start;
  padding: var(--cb-space-md) var(--cb-space-xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-2xl);
  background: transparent;
  color: var(--cbc-ink-muted);
  font-size: var(--cb-type-sm-size);
  cursor: pointer;
}
.cb-report-sched-toggle:hover {
  background: var(--cbc-hover);
  color: var(--cbc-ink);
}
.cb-report-sched-toggle[aria-checked="true"] {
  border-color: var(--cbc-accent);
  background: var(--cbc-accent-quiet);
  color: var(--cbc-ink);
}
.cb-report-sched-toggle[aria-disabled="true"] {
  opacity: 0.5;
  cursor: default;
}
.cb-report-sched-rest {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cb-space-lg);
}

/* ---- SHARED, OR KEPT ----
   Two answers side by side rather than a checkbox saying "private", because
   the second is an ordinary choice and not the absence of the first. Each
   carries what it MEANS under its name: "shared" and "private" are exactly the
   words everybody thinks they already understand, and the sentence underneath
   is the part that stops somebody putting an unfinished question in front of
   the whole team.

   The chosen one is marked by ink and edge rather than by a filled ground: this
   sits inside a dialog that already has one primary button, and a second filled
   thing beside it would compete for the same press. */
.cb-report-share {
  display: grid;
  /* Two equal columns rather than an auto-fit with a minimum width: there are
     exactly two answers and there will only ever be two, so a track size is a
     number that would have to be invented. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--cb-space-lg);
}
.cb-report-share-pick {
  display: grid;
  gap: var(--cb-space-xs);
  align-content: start;
  text-align: start;
  padding: var(--cb-space-xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-2xl);
  background: transparent;
  color: var(--cbc-ink-muted);
  cursor: pointer;
  transition:
    border-color var(--cb-motion-fast) var(--cbc-spring),
    background var(--cb-motion-fast) var(--cbc-spring);
}
.cb-report-share-pick:hover {
  background: var(--cbc-hover);
}
.cb-report-share-pick > strong {
  font-size: var(--cb-type-sm-size);
  font-weight: var(--cb-font-weight-semibold);
  color: var(--cbc-ink);
}
.cb-report-share-pick > span {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  line-height: var(--cb-type-xs-line-height);
}
.cb-report-share-pick[aria-checked="true"] {
  border-color: var(--cbc-accent);
  background: var(--cbc-accent-quiet);
}
@media (prefers-reduced-motion: reduce) {
  .cb-report-share-pick {
    transition: none;
  }
}

/* ---- OUT OF CHARACTER ----
   A mark, not an alarm. It says this row's last reading is unlike this row's
   own usual, and it deliberately takes no view on whether that is good: up is
   welcome on scans finished and unwelcome on cost, and this stylesheet knows
   which measure it is drawn beside no better than unusual.ts does. So it is
   drawn in the console's own ink at the weight of a footnote, and the sentence
   that explains it lives on the tooltip rather than on the screen, where it
   would be twelve sentences in twelve rows.

   Direction is carried by the glyph rather than by colour, because colour here
   would have to mean good or bad to be worth anything, and it cannot. */
.cb-report-odd {
  display: inline-block;
  margin-inline-start: var(--cb-space-xs);
  font-size: var(--cb-type-xs-size);
  line-height: 1;
  color: var(--cbc-ink-faint);
  cursor: default;
}
.cb-report-odd:hover,
.cb-report-odd:focus-visible {
  color: var(--cbc-ink);
}

/* ---- A ROW'S OWN LINE ----
   Under the figure it is a line of, at the same short width the gauge uses, so
   a column carrying both reads as one object per row rather than as two marks
   that happen to be near each other. It never sets the row's height: a table
   whose rows grow because a trend appeared is a table that moves under the
   pointer the first time somebody sorts it. */
/* ---- A ROW'S OWN LINE ----
   THE DRAWING IS BORROWED, THE BOX IS NOT. sparkline() is written for a
   dashboard tile, where .cb-spark is a BACKGROUND: absolutely positioned,
   pinned to the bottom of the tile, at 0.3 so it reads as texture behind a big
   number. A table cell is not a positioned ancestor, so dropped in whole it
   escaped to the card and painted one wandering red line across the foot of the
   entire table. So the svg is lifted OUT of that wrapper (row.ts) into this
   one, which is an ordinary block in the cell.

   ITS ROOM IS KEPT ON EVERY ROW OF THE COLUMN, empty where a row had nothing
   to draw, because rows of two different heights in one table is the thing
   this was never allowed to cost (keepsRoom in row.ts).

   AND IT IS INK, NOT ACCENT. The accent in this product means "the figure you
   are being pointed at"; twelve red scribbles down a column point at nothing
   and compete with the bar chart above, which is the real drawing on this
   screen. Faint, under the number it belongs to (rule 14: hold the rest back). */
.cb-report-trend {
  display: block;
  inline-size: var(--cb-space-8xl);
  block-size: var(--cb-space-xl);
  margin-block-start: var(--cb-space-xxs);
  margin-inline-start: auto;
  color: var(--cbc-ink-faint);
}
.cb-report-trend .cb-spark-svg {
  inline-size: 100%;
  block-size: 100%;
  display: block;
}
.cb-report-trend .cb-chart-line {
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-dasharray: none;
  fill: none;
}
.cb-report-trend .cb-chart-area {
  fill: currentColor;
  opacity: 0.18;
  stroke: none;
}
/* The end mark says which reading is the latest one, which is the whole
   question a trend in a table is asked. It keeps the ink, not the accent. */
.cb-report-trend .cb-spark-end {
  stroke: currentColor;
  stroke-width: 3;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* ---- WHILE IT IS STILL A QUESTION ----
   The strip and the plot keep their shape while the server folds. Emptying
   them was what the screen used to do, and it meant the top two thirds
   collapsed on every edit and refilled a moment later: the answer arrived by
   the page jumping rather than by the figures changing.

   THE MARK IS THIN AND THE ROW CARRIES THE HEIGHT. A grey slab the size of the
   figure it stands in for reads as a thing; a thin mark in a row of the right
   height reads as a place where a thing goes (skeleton-shapes.ts arrived at
   this the hard way). Both rows are the real type's own line height, so the
   grey tile and the tile of figures are the same picture and one dissolves
   into the other. */
.cb-report-stand-dt {
  display: flex;
  align-items: center;
  block-size: var(--cb-type-xs-line-height);
}
.cb-report-stand-dd {
  display: flex;
  align-items: center;
  block-size: var(--cb-type-xl-line-height);
}
/* The plot's two rows: where the measure menu sits, and the marks under it. */
.cb-report-stand-pick {
  display: flex;
  justify-content: flex-end;
}
.cb-report-stand-bars {
  display: grid;
  align-content: center;
  gap: var(--cb-space-2xl);
}

/* ---- A REPORT THAT CAME BACK WITH NOTHING ----
   It keeps the plot's own reserved height, the same expression and for the same
   reason: a run that answers nothing must leave the card exactly the size the
   last one did, or every empty result drags the screen up under the pointer.
   Its own classes rather than the console's shared empty state, which is sticky
   for a scrolling panel and hides its detail line inside a narrow card; here
   that line is the one carrying the next move (empty.ts). */
.cb-report-empty {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: var(--cb-space-sm);
  min-block-size: calc(var(--cbc-card-row) * 2);
  padding: var(--cb-space-3xl);
  text-align: center;
  animation: cbc-emerge var(--cb-motion-slow) var(--cbc-spring) backwards;
}
@media (prefers-reduced-motion: reduce) {
  .cb-report-empty {
    animation: none;
  }
}
/* Drawn in the line colour rather than the ink: it is furniture around the
   sentence, not a thing to read first. */
.cb-report-empty-mark {
  inline-size: var(--cb-space-6xl);
  block-size: var(--cb-space-6xl);
  color: var(--cbc-line-strong);
}
.cb-report-empty-head {
  margin: 0;
  font-size: var(--cb-type-base-size);
  font-weight: var(--cb-font-weight-semibold);
}
.cb-report-empty-line,
.cb-report-empty-near {
  margin: 0;
  max-inline-size: var(--cb-width-xxs);
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-type-sm-line-height);
  color: var(--cbc-ink-faint);
}
/* The value that WOULD have answered, held back from the sentence above it so
   the two do not read as one paragraph. */
.cb-report-empty-near {
  color: var(--cbc-ink-muted);
}
.cb-report-empty > .cb-btn {
  margin-block-start: var(--cb-space-sm);
}

/* THE PLOT'S OWN STRIP. The measure menu used to sit alone at the right of an
   otherwise empty row, so the top of the biggest card on the screen was blank.
   The caption says what the bars are cut by; the menu says which column is
   drawn. Two halves of one sentence, at the two ends of one row. */
.cb-report-plot-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cb-space-xl);
}
.cb-report-plot-cut {
  margin: 0;
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-medium);
  text-transform: uppercase;
  letter-spacing: var(--cb-tracking-xxs);
  color: var(--cbc-ink-faint);
}
/* Which of up to twelve measures is drawn, at the end of its own row. A row
   of chips here would be a second chart above the first one. */
.cb-report-pick {
  display: flex;
  justify-content: flex-end;
}
.cb-report-pick .cb-listbox {
  inline-size: var(--cb-space-11xl);
}

/* ---- THE THREE THINGS YOU DO WITH AN ANSWER ----
   Take it away, keep it, or open one somebody already kept. All three hang
   off the page header, and none of them may change that header's width as its
   own state changes: the count of saved reports goes inside the menu, never
   on the button that opens it. */
.cb-report-export {
  position: relative;
  display: inline-flex;
  align-items: center;
}
/* A file being written says so in place. Swapping the button for a spinner
   changes its width mid-press, which moves the control out from under the
   pointer that is still on it. */
.cb-report-export[data-busy] {
  opacity: 0.6;
  pointer-events: none;
}
/* The reports the team has kept: a list, not a row of chips, because these
   are named things with an owner and a sentence and the sentence is usually
   the useful half. It scrolls inside itself, because the contract's ceiling is
   two hundred and a menu two hundred rows tall is not a menu. */
.cb-report-saved {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--cb-space-xxs);
  max-block-size: var(--cb-width-sm);
  overflow-y: auto;
}
.cb-report-saved small {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}
/* A ROW IS A BUTTON AND A BIN, and the button carries three lines: the name,
   the sentence, and who saved it. All of it went unstyled until the class guard
   learned to look for names no stylesheet mentions, so a native button centred
   its own text and ran the three lines together into one paragraph reading
   "Where scans breakStage by stage, worst first.Aaron · 3 days ago". */
.cb-report-saved-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--cb-space-xs);
  border-radius: var(--cb-radius-md);
}
.cb-report-saved-row:hover {
  background: var(--cbc-hover);
}
/* The report the builder is currently showing. Marked rather than selected:
   there is no choosing here, only opening, so it says which one you are in. */
.cb-report-saved-row[data-open] {
  background: var(--cbc-hover);
}
/* The whole left side is the target. A name is a short thing to aim at and the
   sentence under it is the half people recognise, so the sentence opens it too. */
.cb-report-saved-open {
  display: grid;
  gap: var(--cb-space-xxs);
  min-inline-size: 0;
  padding: var(--cb-space-md) var(--cb-space-lg);
  border: 0;
  border-radius: inherit;
  background: none;
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}
.cb-report-saved-open[aria-disabled="true"] {
  cursor: default;
  opacity: 0.55;
}
/* The name and its badge share a line, and the badge never shrinks: "Open,
   edited" wrapping under the name reads as a second report rather than as a
   state of this one. */
.cb-report-saved-name {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xs);
  min-inline-size: 0;
  font-size: var(--cb-type-sm-size);
  font-weight: var(--cb-font-weight-semibold);
}
.cb-report-saved-name > .cb-badge {
  flex: none;
}
/* One line each, clipped. The contract caps the note at 200 characters, which
   is four lines in this width: a menu whose rows are four lines tall is a menu
   somebody scrolls instead of scans. The full text is in the tooltip. */
.cb-report-saved-note,
.cb-report-saved-who {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--cb-type-xs-size);
}
.cb-report-saved-note {
  color: var(--cbc-ink-muted);
}
.cb-report-saved-who {
  color: var(--cbc-ink-faint);
}
/* The bin appears on approach rather than sitting in every row (rule 14: one
   drag handle, not seven). It keeps its space either way, so nothing moves when
   it arrives, and it stays put for a keyboard, which never hovers anything. */
.cb-report-saved-row .cb-btn {
  opacity: 0;
  transition: opacity var(--cb-motion-fast) var(--cb-motion-ease);
}
.cb-report-saved-row:hover .cb-btn,
.cb-report-saved-row:focus-within .cb-btn {
  opacity: 1;
}
/* Saving is the one thing here that is not opening something, so it sits under
   a line rather than in the list. Sunken like the head, so the scrolling part
   is visibly the part between them. */
.cb-report-saved-foot {
  padding: var(--cb-space-md) var(--cb-space-lg);
  border-block-start: 1px solid var(--cbc-line);
  background: var(--cbc-sunken);
}
/* "Nothing here" is two sentences and they are not the same weight: the first
   is the state, the second says what the feature is for. */
.cb-report-saved-empty {
  display: grid;
  gap: var(--cb-space-md);
  padding: var(--cb-space-xl) var(--cb-space-lg);
}
/* SAVING IS PUBLISHING, and the dialog says so in words. What the stylesheet
   owes that sentence is a line of its own under the two fields: who else will
   see this is not a hint hanging off a text box. */
.cb-report-save {
  display: grid;
  gap: var(--cb-space-2xl);
  inline-size: min(var(--cb-width-sm), 100%);
}

/* 768px mirrors tokens.breakpoint.md */
@media (max-width: 768px) {
  /* The fixed slots are what keep the bar still on a monitor. On a narrow
     window there is no room left for them to be still IN, so each control
     takes the line instead: stacked and legible beats a grid of half-width
     menus with their labels cut off. */
  /* There is no room left for the fixed slots to be still IN, so each control
     takes the line instead, and the hairlines that grouped them go with the
     grouping: a divider between two things stacked vertically is a rule across
     the middle of a form. */
  .cb-report-cluster {
    flex: 1 1 100%;
  }
  .cb-report-div {
    display: none;
  }
  .cb-report-slot {
    flex: 1 1 100%;
  }
  .cb-listbox-wrap {
    flex: 1 1 100%;
  }
  .cb-listbox {
    flex: 1 1 auto;
    inline-size: auto;
  }
  .cb-report-slot > .cb-input {
    flex: 1 1 auto;
    inline-size: auto;
  }
  .cb-report-totals {
    grid-template-columns: minmax(0, 1fr);
  }
  .cb-report-pick {
    justify-content: flex-start;
  }
}

/* ---- THE ANSWER ARRIVES AS ONE THING ----
   The gallery has always had a proper staggered entrance and the answer had
   almost none: only the plot revealed itself, so the strip, the table and the
   note simply appeared while the picture between them animated. One
   orchestrated arrival instead, in reading order, off the same keyframe and
   the same queue the gallery uses.

   --cbc-inside RATHER THAN --cbc-step, because these are inside a container
   that is itself still arriving on the first run: a queue starting the instant
   the box does happens entirely underneath it (console-theme.css).

   ONLY WHEN THE RUN SAYS SO. The flag is put on by the draw and taken off when
   the entrance ends, so re-sorting a column or re-plotting a measure does not
   replay it: those are the same reading a moment later, and a screen that
   animates every time a number is read is a screen nobody can read. */
.cb-report-answer > * {
  animation: cbc-emerge var(--cb-motion-slow) var(--cbc-spring) backwards;
  animation-delay: var(--cbc-inside);
}
@media (prefers-reduced-motion: reduce) {
  .cb-report-answer > * {
    animation: none;
  }
}
