/* Console: how information is displayed. Cards, statistics, tables, badges,
   the submission detail layout, and the code view. Values are --cb-* tokens
   (see console.css header for the documented exceptions). */

/* ---- Grids ---- */
.cb-grid {
  display: grid;
  gap: var(--cb-space-3xl);
}
.cb-grid[data-cols="3"] {
  grid-template-columns: repeat(auto-fit, minmax(var(--cb-width-xxs), 1fr));
}
.cb-grid[data-cols="2"] {
  grid-template-columns: repeat(auto-fit, minmax(var(--cb-width-sm), 1fr));
}

/* ---- Statistics ---- */
.cb-stat-label {
  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-accent);
}
.cb-stat-value {
  display: block;
  margin-top: var(--cb-space-md);
  font-size: var(--cb-type-4xl-size);
  line-height: var(--cb-type-4xl-line-height);
  letter-spacing: var(--cb-type-4xl-letter-spacing);
  font-weight: var(--cb-font-weight-semibold);
  color: var(--cbc-ink);
}
.cb-stat-note {
  font-size: var(--cb-type-sm-size);
  color: var(--cbc-ink-faint);
}
.cb-stat-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cb-space-3xl);
  margin-top: var(--cb-space-2xl);
  font-size: var(--cb-type-sm-size);
  color: var(--cbc-ink-muted);
}

/* ---- Badges ---- */
.cb-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-sm);
  padding: var(--cb-space-xxs) var(--cb-space-lg);
  border-radius: var(--cb-radius-full);
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-semibold);
  background: var(--cbc-sunken);
  color: var(--cbc-ink-muted);
  white-space: nowrap;
}
/* The dot carries the colour and the label stays dark ink. Coloured small text
   on a tinted pill does not clear the contrast threshold, and the console is
   read all day by the people who sell accessibility work. */
.cb-badge::before {
  content: "";
  inline-size: var(--cb-space-md);
  block-size: var(--cb-space-md);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-ink-faint);
  flex: none;
}
.cb-badge[data-tone="good"] {
  background: color-mix(in srgb, var(--cbc-good) 12%, transparent);
}
.cb-badge[data-tone="good"]::before {
  background: var(--cbc-good);
}
.cb-badge[data-tone="warn"] {
  background: color-mix(in srgb, var(--cbc-warn) 16%, transparent);
}
.cb-badge[data-tone="warn"]::before {
  background: var(--cbc-warn);
}
.cb-badge[data-tone="poor"] {
  background: color-mix(in srgb, var(--cbc-poor) 10%, transparent);
}
.cb-badge[data-tone="poor"]::before {
  background: var(--cbc-poor);
}

/* ---- Tables ---- */
/* THE TABLE IS ITS OWN SCROLLPORT, BOTH WAYS, and the two halves of that are
   the same decision rather than two.

   It used to be `overflow: visible` for a fixed-layout table, on the reasoning
   that a table sized in percentages never overflows. That stopped being true
   the moment a reader could add columns and drag widths, and it was never true
   of twelve columns on a laptop: the table simply ran off the side of its own
   card and the page grew to fit it.

   Scrolling sideways means a scroll container, and a scroll container cannot
   scroll one axis and leave the other visible: the moment `overflow-x` is auto,
   `overflow-y` is too. That is what makes the height the second half of the
   same decision. Bounded to what is left of the window, the region scrolls both
   ways AND the sticky header finally has something to stick to, which it never
   did while the page was the scrollport for a header inside a card. Short
   tables are unaffected: a max is not a height.

   A trackpad's sideways swipe and shift-and-wheel come free with the container.
   `overscroll-behavior-x` is what stops the second half of a flick becoming the
   browser's back gesture, which is the one way a horizontal scroll can lose
   somebody's place completely. Column-by-column keys are in table-scroll.ts. */
/* ---- THE FRAME: a header strip, and the rows scrolling under it ----
   (views/table-frame.ts, which carries the argument.) The short version is
   that a scrollbar is a picture of what moves, and a header that does not move
   should not have one running past it. So the header is out of the scroller
   and the two halves are kept in step by script.

   IT CLIPS, and that is the second thing this fixes. The card these sit in has
   a twenty pixel radius and cannot clip its own contents (a clipping ancestor
   is a scroll container, which would quietly kill the selection bar's sticky),
   so the header's tint and the first row's hover fill were both running square
   over the card's rounded top corners. The frame clips instead: it is the one
   element here that is allowed to, because nothing inside it is sticky to
   anything outside it. */
.cb-table-frame {
  position: relative;
  overflow: hidden;
  /* The card's own corners, on the element that actually paints into them.
     Only the top two: what sits at the bottom of the card is the foot on one
     screen and the card's own padding on the other, and neither is this. */
  border-start-start-radius: var(--cb-radius-3xl);
  border-start-end-radius: var(--cb-radius-3xl);
}
/* The businesses card ends at the table, so there the frame owns all four. */
.cb-listing > div:last-child > .cb-table-frame {
  border-end-start-radius: var(--cb-radius-3xl);
  border-end-end-radius: var(--cb-radius-3xl);
}
/* THE HEADER, ON ITS OWN GROUND. A tint rather than the card's surface, which
   is what every other header in this console does (`.cb-panel-head`): a header
   row set on the same white as the rows under it is a row that happens to be
   in capitals. Overflow hidden because the strip is scrolled by script to
   follow the rows, never by hand. */
.cb-table-head {
  overflow: hidden;
  background: var(--cbc-hover);
  border-block-end: 1px solid var(--cbc-line);
}
/* The cells take the strip's tint rather than painting their own: they were
   opaque because they used to be the sticky layer inside the scroller, and a
   surface-coloured cell here cuts a white block out of the tint.

   POSITION IS LEFT ALONE, and that is not an oversight. Setting it to `static`
   was the obvious tidy-up (nothing needs to stick to the top of a strip that
   does not scroll vertically) and it broke both resize and reorder: `.cb-col`
   is `position: relative` so the grab handle inside it has the column to
   position against, and `static` here outranked that, which sent every handle
   to the trailing edge of the whole frame at its full height. A sticky element
   is a positioned element, so leaving it alone keeps the handle where it
   belongs AND keeps the actions header pinned as its cells are. */
.cb-table-head .cb-table th {
  background: none;
  border-bottom: 0;
}
.cb-table-wrap {
  position: relative;
  overflow: auto;
  max-block-size: calc(100dvh - var(--cb-space-10xl));
  overscroll-behavior-x: contain;
}
/* A table with no header of its own has nothing to hold its first row off the
   top of the frame, and the first row's rule would otherwise sit against the
   header's. */
.cb-table-frame .cb-table-wrap > .cb-table > tbody > tr:first-child > td {
  border-top: 0;
}
/* A scrolling region has to be reachable by keyboard, or the columns past the
   edge are invisible to anyone not using a mouse. */
.cb-table-wrap:focus-visible {
  outline: var(--cbc-focus);
  outline-offset: var(--cbc-focus-gap);
}
/* THERE IS MORE THAT WAY. A table cut off at the edge with no mark reads as a
   table that ends there, so whichever side has something past it carries a
   shadow at that edge.

   AN INSET SHADOW, NOT A FADE. Masking the container was the first version and
   it faded the CONTENT: the first two letters of every website in the leftmost
   column went pale, which is a table apologising for itself. An inset shadow is
   painted on the container's own box and stays where it is put while the rows
   go past underneath, which is what a scroll edge should do. */
.cb-table-frame,
.cb-table-wrap {
  --cb-edge: var(--cb-space-lg);
  transition: box-shadow var(--cb-motion-fast) var(--cb-motion-ease);
}
.cb-table-frame[data-more="start"],
.cb-table-wrap[data-more="start"] {
  box-shadow: inset var(--cb-edge) 0 var(--cb-edge) calc(-1 * var(--cb-edge)) var(--cbc-line-strong);
}
.cb-table-frame[data-more="end"],
.cb-table-wrap[data-more="end"] {
  box-shadow: inset calc(-1 * var(--cb-edge)) 0 var(--cb-edge) calc(-1 * var(--cb-edge))
    var(--cbc-line-strong);
}
.cb-table-frame[data-more="both"],
.cb-table-wrap[data-more="both"] {
  box-shadow:
    inset var(--cb-edge) 0 var(--cb-edge) calc(-1 * var(--cb-edge)) var(--cbc-line-strong),
    inset calc(-1 * var(--cb-edge)) 0 var(--cb-edge) calc(-1 * var(--cb-edge))
    var(--cbc-line-strong);
}
@media (prefers-reduced-motion: reduce) {
  .cb-table-frame,
  .cb-table-wrap {
    transition: none;
  }
}
.cb-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--cb-type-sm-size);
}
.cb-table th {
  text-align: start;
  /* The one value the density preference moves; see console-theme.css. */
  padding: var(--cbc-row-y) var(--cb-space-xl);
  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);
  border-bottom: 1px solid var(--cbc-line);
  white-space: nowrap;
}
.cb-table td {
  padding: var(--cbc-row-y) var(--cb-space-xl);
  border-bottom: 1px solid var(--cbc-line);
  vertical-align: middle;
}
/* A log is read down a column, so the last row does not need a rule under it
   separating it from nothing. */
.cb-table tbody tr:last-child td {
  border-bottom: 0;
}
.cb-table tbody tr {
  transition: background var(--cb-motion-fast) var(--cbc-spring);
}
/* Hover used to be the heaviest fill on the screen, which read as a committed
   selection rather than as the pointer passing over. Selection is the stronger
   state now, because it is the one that means something. */
.cb-table tbody tr:hover {
  background: var(--cbc-hover);
}
.cb-table tbody tr[data-clickable] {
  cursor: pointer;
}
.cb-table tbody tr[data-selected] {
  background: var(--cbc-accent-wash);
}
.cb-table tbody tr:focus-visible {
  outline: var(--cbc-focus);
  outline-offset: var(--cbc-focus-inset);
}

/* ---- A table you work in ---- */
/* Fixed layout, so the columns are the widths the header declares rather than
   whatever the longest value in them happens to be. Without it "3m 58s" was
   wrapping onto two lines inside a column narrower than the value itself. */
.cb-table-fixed {
  table-layout: fixed;
}
/* ONCE A COLUMN HAS BEEN DRAGGED, the table is the widths somebody set rather
   than a share of the window, and it scrolls sideways if they set them wider
   than the screen. Left at 100% it is not: a browser laying out a fixed table
   has exactly one table-width to hand out, so one column asking for 294 pixels
   among six asking for percentages does not get 294 pixels, it gets everything
   scaled down to fit, and the column comes out NARROWER than it was before the
   drag. The table's own width is then written out in pixels as the sum of its
   columns (audit-table.ts), which is the only version of this where every
   column ends up the width it was told: `auto` and `max-content` both leave the
   browser distributing the difference, which it does proportionally, so six
   columns nobody touched all move whenever one is dragged. */
.cb-table-fixed td {
  height: var(--cb-space-8xl);
}
/* Nothing in a row wraps. One wrapped cell is what made the whole list ragged. */
.cb-table-fixed td,
.cb-table-fixed th {
  white-space: nowrap;
}
.cb-table-fixed td .cb-inline {
  flex-wrap: nowrap;
}
/* Truncated, never wrapped. One row height is what lets the eye run down a
   column; the full value is a hover away. */
.cb-cell-clip {
  overflow: hidden;
}
.cb-clip {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.cb-cell-clip .cb-inline {
  flex-wrap: nowrap;
  min-width: 0;
}

/* The one cell in a row that opens something, where the row itself cannot: a
   table with a checkbox column has already spent its click. Underlined only on
   approach, so a column of five hundred is a column of names rather than a
   column of links (rule 14: the affordance arrives when you do). */
a.cb-cell-link {
  color: inherit;
  text-decoration: none;
}
a.cb-cell-link:hover {
  color: var(--cbc-accent);
  text-decoration: underline;
}
a.cb-cell-link:focus-visible {
  color: var(--cbc-accent);
}

/* Thirty rows in you no longer know which number is the score and which is the
   cost. */
.cb-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--cbc-surface);
}

.cb-sort {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-sm);
  border: 0;
  padding: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  cursor: pointer;
}
.cb-sort:hover {
  color: var(--cbc-ink);
}
.cb-sort[data-sorted] {
  color: var(--cbc-ink);
}
.cb-cell-num .cb-sort {
  justify-content: flex-end;
  width: 100%;
}
/* NO SECOND MARK. There was a `::after` here drawing a masked chevron on every
   sortable header, revealed on approach, and it was right until the sort mark
   below replaced it: one cell holding three lucide glyphs, showing the one that
   applies. Nothing removed the old one, so every header in the console drew
   BOTH of them, and a column read "WEBSITE ⌃⌄ ⌄" — a mark saying the column can
   be sorted, next to a mark saying nothing at all. Its `[data-active]` state
   had been dead since the same change, which is how it survived a year: the
   attribute is set nowhere, so the leftover only ever showed at 45% on hover
   and read as part of the design.

   The affordance the old rule existed for is unchanged and lives in the mark
   itself: the room is held open from the first frame, the faint two-way chevron
   answers the pointer, and a sorted column keeps its ink and its direction. */

/* ---- The sort mark (views/table-sort.ts), worn by every table here ----

   THE ROOM IS HELD OPEN WHETHER A GLYPH IS SHOWING OR NOT. Three marks live in
   one cell and CSS shows the one that applies, so approaching a column cannot
   move the words in the header beside it (rule 14), and the mark can fade in
   rather than appearing from nowhere. */
.cb-sort-mark {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-xxs);
  flex: none;
}
.cb-sort-glyphs {
  display: inline-grid;
  place-items: center;
  inline-size: var(--cb-space-lg);
  block-size: var(--cb-space-lg);
}
.cb-sort-glyphs .cb-icon {
  grid-area: 1 / 1;
  inline-size: var(--cb-space-lg);
  block-size: var(--cb-space-lg);
  opacity: 0;
  transition: opacity var(--cb-motion-fast) var(--cbc-spring);
}
/* A column that can be sorted says so when the pointer or the keyboard arrives
   on it, and not before: a two-way chevron on every header at rest is six
   things claiming to be the answer. It is deliberately NOT the direction the
   table is currently sorted in. That was the old hint on the dashboard card and
   it pointed the wrong way on every text column, because it was built from
   another column's direction rather than from the one this column opens in. */
.cb-sort:not([data-sorted]):hover .cb-sort-idle,
.cb-sort:not([data-sorted]):focus-visible .cb-sort-idle {
  opacity: 0.5;
}
.cb-sort[data-sorted="asc"] .cb-sort-asc,
.cb-sort[data-sorted="desc"] .cb-sort-desc {
  opacity: 1;
}
/* Which level this column is, and only once there is more than one of them. */
.cb-sort-rank {
  font-size: var(--cb-type-xs-size);
  font-variant-numeric: tabular-nums;
  color: var(--cbc-ink-faint);
}
.cb-sort-rank:empty {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .cb-sort-glyphs .cb-icon {
    transition: none;
  }
}

/* ---- A header that is a control (list-head.ts) ----

   The two list screens and the dashboard card offer the same four gestures on
   a header, so they wear the same furniture: a grab handle at the trailing
   edge, a line saying where a dragged column would land, and the shared sort
   mark above. What differs is only that these are `<th>` elements in a
   fixed-layout table and the card is flex boxes. */
.cb-col {
  position: relative;
}
/* CLIPPED TO ITS OWN COLUMN, header included. A right-aligned heading in a
   narrow column ("AI COST" in seven per cent) overflows to the LEFT, and every
   `th` here is sticky, so it painted over its neighbour and took the pointer
   with it: the neighbour's resize handle could not be pressed at all, because
   the word from the column after it was on top. The rows have clipped since
   they were built (`cb-clip`); the headers were the half nobody checked. */
.cb-col .cb-sort,
.cb-col-word {
  display: flex;
  max-inline-size: 100%;
  min-inline-size: 0;
  overflow: hidden;
}
.cb-cell-num .cb-sort {
  justify-content: flex-end;
}
/* Tighter than the card's, because a heading in a table has a column's width to
   say itself in and the mark is competing with it for that width. */
.cb-col .cb-sort {
  gap: var(--cb-space-xxs);
}
.cb-col .cb-sort-word {
  overflow: hidden;
  text-overflow: ellipsis;
}
/* WHOLLY INSIDE THE COLUMN, and that is not a detail. Straddling the edge is
   where a resize handle wants to be, and in a table it does not work: every
   `th` here is sticky, so a later one paints over the half of the handle that
   hangs into it, and a press aimed at the edge of the status column was landing
   on the score column instead. Nothing looked wrong; the drag simply never
   started. The last few pixels of the column itself are unambiguous. */
.cb-col-grab {
  position: absolute;
  inset-block: 0;
  inset-inline-end: 0;
  inline-size: var(--cb-space-lg);
  cursor: col-resize;
  touch-action: none;
  opacity: 0;
  transition: opacity var(--cb-motion-fast) var(--cbc-spring);
}
.cb-col-grab::after {
  content: "";
  position: absolute;
  inset-block: var(--cb-space-sm);
  inset-inline-end: 0;
  inline-size: 1px; /* a hairline, not a spacing step */
  background: var(--cbc-line-strong);
}
/* One at a time, and only once the pointer is on the row: eight grab marks
   sitting there all afternoon is the opposite of a table you can read. */
.cb-table thead tr:hover .cb-col:hover .cb-col-grab,
.cb-col:focus-within .cb-col-grab {
  opacity: 1;
}
.cb-table thead tr[data-sizing] {
  cursor: col-resize;
  user-select: none;
}
.cb-table thead tr[data-moving] {
  user-select: none;
}
.cb-table thead tr[data-moving] .cb-col {
  cursor: grabbing;
}
.cb-col[data-moving] {
  opacity: 0.4;
}
/* Where it would land, drawn on the column being passed rather than as a
   floating line, so it cannot end up a pixel out from what it describes. */
.cb-col[data-landing]::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inline-size: 2px; /* a focus-ring weight, not a spacing step */
  background: var(--cbc-accent);
}
.cb-col[data-landing="before"]::after {
  inset-inline-start: 0;
}
.cb-col[data-landing="after"]::after {
  inset-inline-end: 0;
}
@media (prefers-reduced-motion: reduce) {
  .cb-col-grab {
    transition: none;
  }
}

.cb-cell-check {
  width: var(--cb-space-6xl);
}

/* ---- THE ACTIONS COLUMN (views/row-acts.ts) ----
   Last in the row, and NOTHING ELSE. No ground of its own, no wash of its own,
   nothing pinned: it takes the row's background exactly as the six cells beside
   it do, because it is part of the same row.

   It was pinned to the trailing edge, on the reasoning that a table wide enough
   to scroll should keep its actions in reach. That reasoning cost more than it
   bought and every cost was the same cost. A pinned cell has to paint an opaque
   ground or the rows slide under it; an opaque ground hides the row's own fill,
   so the fill has to be painted back on; painted back as a background-image it
   cannot transition, so it snapped on while the row eased in; fixed as a layer
   it matched to the pixel and STILL read as a separate block, because it was
   one, sitting on top of the row covering the end of a cost figure and casting
   a shadow to say so.

   Three fixes for one decision is the decision being wrong. A column that
   scrolls with its row is what every other column here does, and reaching it on
   a wide table is the same gesture as reading any other column on a wide table.

   The width is the one thing left: three glyphs, their gaps and the cell's own
   padding, so the column holds them without the row going ragged. */
.cb-cell-acts {
  width: var(--cb-space-9xl);
  padding-inline: var(--cb-space-md);
}
.cb-rowacts {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--cb-space-xxs);
}
/* THEY WAIT UNTIL THE POINTER IS ON THE ROW (rule 14: a control shows its
   affordance when you approach it and not before). Three glyphs on every one of
   fifty rows is a hundred and fifty marks competing with the data. They keep
   their room whether they are showing or not, so nothing moves when they do,
   and the keyboard brings them up the same way the pointer does. */
.cb-rowact {
  display: inline-flex;
  padding: var(--cb-space-xs);
  border: 0;
  border-radius: var(--cb-radius-md);
  background: none;
  color: var(--cbc-ink-faint);
  cursor: pointer;
  opacity: 0;
  /* The duration here is the way OUT. Coming in is instant; see below. */
  transition:
    opacity var(--cb-motion-fast) var(--cbc-spring),
    background var(--cb-motion-fast) var(--cbc-spring),
    color var(--cb-motion-fast) var(--cbc-spring);
}
/* IN AT ONCE, OUT ON A FADE, and the asymmetry is the point.

   Both this and the row's own fill were 150ms on the same curve, and they did
   not read as the same: the row goes from nothing to an eight per cent wash, so
   at forty per cent through the transition it already looks arrived, while a
   glyph at forty per cent opacity still reads as one that has not turned up.
   Identical clocks, different perceived arrivals, and the actions looked late
   on every row the pointer crossed.

   So the affordance answers the pointer immediately (rule 14) and the fade is
   kept for the way out, where it is doing something useful: running down a list
   of rows, the one you have just left dims out behind you rather than snapping
   off. Only the opacity is zeroed; the button's own hover fill and ink still
   ease, because those are a response to the pointer arriving on the BUTTON and
   nothing has to catch up with anything. */
.cb-table tbody tr:hover .cb-rowact,
.cb-table tbody tr:focus-within .cb-rowact,
.cb-rowact:focus-visible {
  opacity: 1;
  transition-duration: 0s, var(--cb-motion-fast), var(--cb-motion-fast);
}
.cb-rowact:hover {
  background: var(--cbc-hover-strong);
  color: var(--cbc-ink);
}
.cb-rowact[data-danger]:hover {
  color: var(--cbc-poor-text);
}
/* A DISABLED ACTION IS DIMMER THAN THE OTHERS, AND STILL WAITS ITS TURN.
   `opacity: .35` used to sit on the disabled rule alone, which comes after the
   `opacity: 0` above it at the same specificity, so a turned-off action was
   visible on every row at rest while its two neighbours stayed hidden: one
   stuck glyph floating on a row nobody was pointing at. It shows on the trail
   (an entry whose change has no screen of its own) and on the businesses list
   for anybody in a viewer seat, which is every row on that screen. */
.cb-rowact[aria-disabled="true"] {
  cursor: not-allowed;
}
.cb-table tbody tr:hover .cb-rowact[aria-disabled="true"],
.cb-table tbody tr:focus-within .cb-rowact[aria-disabled="true"],
.cb-rowact[aria-disabled="true"]:focus-visible {
  opacity: 0.35;
}
.cb-rowact .cb-icon {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}
@media (prefers-reduced-motion: reduce) {
  .cb-rowact {
    transition: none;
  }
}
/* Shape and colour come from the drawn checkbox in console-forms.css; this is
   only where it sits. `accent-color` used to be the whole of it, which tinted
   the browser's own box and left its shape to the platform. */
.cb-check {
  margin-inline: auto;
}

/* A SET, drawn as a set: every stage a package can run, wrapping, with the box
   and its words on one line each. Not .cb-check, which is the table's own
   centring rule for a select-row box and means something else entirely. */
.cb-stage-picks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cb-space-lg) var(--cb-space-3xl);
}
.cb-stage-pick {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-md);
  font-size: var(--cb-type-sm-size);
  color: var(--cbc-ink-muted);
  cursor: pointer;
}
.cb-stage-pick:hover {
  color: var(--cbc-ink);
}

/* A count of failed stages, as a mark rather than a sentence. The sentence is
   what used to push every third row onto two lines. */
.cb-issue {
  flex: none;
  padding: 0 var(--cb-space-sm);
  border-radius: var(--cb-radius-xs);
  background: color-mix(in srgb, var(--cbc-warn) 18%, transparent);
  color: var(--cbc-ink);
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-semibold);
  font-variant-numeric: tabular-nums;
}

/* A scan that has not finished says so by breathing.
   A queued audit, a running one and a finished one were three dots of three
   colours and nothing else, so the one row on the screen that is still moving
   looked exactly as settled as the two hundred above it. Only the status
   badge's dot, and only in those two states: the score badge in the next
   column but one has a dot of its own, which is why the cell is named.
   Opacity only, and slowly (console-theme.css on cbc-breathe): a mark that
   swells reads as an alarm demanding attention, and a mark that comes and goes
   reads as something quietly getting on with it. */
.cb-table tbody tr[data-status="running"] .cb-cell-status .cb-badge::before,
.cb-table tbody tr[data-status="queued"] .cb-cell-status .cb-badge::before {
  --cbc-breath: 0.3;
  animation: cbc-breathe var(--cbc-loop-calm) var(--cb-motion-ease) infinite;
}

/* ---- Filters, views, counts, bulk actions ---- */
.cb-views {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cb-space-sm);
  padding: 0 var(--cb-space-3xl);
}
.cb-view {
  padding: var(--cb-space-xs) var(--cb-space-xl);
  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);
  cursor: pointer;
  transition:
    color var(--cb-motion-fast) var(--cbc-spring),
    border-color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-view:hover {
  color: var(--cbc-ink);
  border-color: var(--cbc-line-strong);
}
.cb-view[aria-pressed="true"] {
  color: var(--cbc-ink);
  border-color: var(--cbc-accent);
  background: var(--cbc-accent-wash);
}
.cb-count {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xs);
  padding: var(--cb-space-lg) var(--cb-space-3xl) 0;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}
/* The count line with an action on its far side. The sentence keeps its own
   padding and the button is pushed out, so the two do not need to agree about
   spacing and the line reads as one thing rather than as a toolbar. */
.cb-count-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--cb-space-lg);
  padding-inline-end: var(--cb-space-3xl);
}
.cb-count-line .cb-count {
  flex: 1;
  min-width: 0;
}
.cb-bulk {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--cb-space-xl);
  margin: var(--cb-space-lg) var(--cb-space-3xl) 0;
  padding: var(--cb-space-lg) var(--cb-space-2xl);
  border-radius: var(--cb-radius-xl);
  background: var(--cbc-accent-wash);
  font-size: var(--cb-type-sm-size);
  animation: cbc-rise var(--cb-motion-base) var(--cbc-spring) backwards;
}

/* The condition on a count, as a mark rather than as a sentence (caveat.ts).
   It is quieter than the number it qualifies until somebody goes near it, which
   is the whole point: the count is what is being read, and the condition is
   what they ask about once. */
.cb-caveat {
  display: inline-flex;
  flex: none;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  opacity: 0.55;
  cursor: help;
  transition: opacity var(--cb-motion-fast) var(--cbc-spring);
}
.cb-caveat:hover,
.cb-caveat:focus-visible {
  opacity: 1;
}
.cb-caveat .cb-icon {
  inline-size: var(--cb-space-lg);
  block-size: var(--cb-space-lg);
}
@media (prefers-reduced-motion: reduce) {
  .cb-caveat {
    transition: none;
  }
}

/* A credential leaving, or someone's access changing, is the entry you go
   looking for months later. It should not be typographically identical to a
   settings save. */
.cb-table tbody tr[data-weight="high"] .cb-cell-strong {
  color: var(--cbc-ink);
}
/* Marked by the weight of its own words rather than a bar down the edge of the
   first cell. See docs/10 on rails. */
.cb-table tbody tr[data-weight="high"] > td:first-child {
  font-weight: var(--cb-font-weight-semibold);
}

/* A summary you can open, for a list whose answer is one sentence and whose
   detail is eleven rows of the same word. */
.cb-fold > summary {
  display: flex;
  align-items: baseline;
  gap: var(--cb-space-lg);
  padding: var(--cb-space-lg) var(--cb-space-3xl);
  cursor: pointer;
  font-size: var(--cb-type-sm-size);
  list-style: none;
}
.cb-fold > summary::-webkit-details-marker {
  display: none;
}
.cb-fold-more {
  margin-inline-start: auto;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}
.cb-fold[open] .cb-fold-more {
  color: var(--cbc-accent);
}
.cb-fold > summary:hover .cb-fold-more {
  color: var(--cbc-ink);
}
/* Several things in one cell (a badge plus a note) sit on one line with air
   between them, rather than being spaced with literal spaces in the text. */
.cb-inline {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--cb-space-md);
}
.cb-table .cb-cell-strong {
  font-weight: var(--cb-font-weight-medium);
}
.cb-table .cb-cell-muted {
  color: var(--cbc-ink-faint);
}
.cb-table .cb-cell-num {
  text-align: end;
  font-variant-numeric: tabular-nums;
}
/* Identifiers and stored values: monospace, so a key ending or a setting name
   is scannable rather than something to squint at. */
.cb-table .cb-cell-code {
  font-family: var(--cb-font-mono);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}

.cb-empty {
  padding: var(--cb-space-4xl);
  text-align: center;
  color: var(--cbc-ink-faint);
  font-size: var(--cb-type-sm-size);
}

/* The recent-scan feed's own rules live in console-charts.css, beside the rest
   of the dashboard. There was a second, older set here from when the card was a
   four-column table; it loaded after and won, so the five-column feed lost its
   last column to a second line on every row. */

/* The period switch in the dashboard header. */
/* A group of options with no options in it is not a control. This is a pill:
   a border, full radius and a few pixels of padding, so before its chips exist
   it drew a small empty circle at the end of the header and read as a stray
   dot appearing ahead of the page. The chips are built once the summary lands. */
.cb-period:empty {
  display: none;
}
.cb-period {
  display: flex;
  gap: var(--cb-space-xs);
  padding: var(--cb-space-xs);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-surface);
}
.cb-period .cb-view {
  border: 0;
}
.cb-period .cb-view[aria-pressed="true"] {
  background: var(--cbc-accent);
  color: var(--cbc-on-accent);
}

/* ---- Submission detail ---- */
.cb-detail-grid {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: var(--cb-space-3xl);
  align-items: start;
}
/* The rail follows you down. The left column runs to three thousand pixels on
   a full audit, and the status, score and reference are exactly what you want
   in view while reading the stages under them. */
.cb-detail-rail {
  position: sticky;
  top: var(--cb-space-3xl);
  display: grid;
  gap: var(--cb-space-2xl);
  align-content: start;
  /* Its own scrollbar if the facts are longer than the window, rather than
     pinning a card taller than the screen and losing its bottom half. */
  max-height: calc(100svh - var(--cb-space-6xl));
  overflow-y: auto;
}

/* A panel with nothing to show. Two hundred pixels of centred grey text three
   times over is most of a screen saying nothing. */
.cb-card-quiet {
  display: flex;
  align-items: baseline;
  gap: var(--cb-space-xl);
  padding: var(--cb-space-2xl) var(--cb-space-3xl);
}
.cb-card-quiet h2 {
  font-size: var(--cb-type-sm-size);
  font-weight: var(--cb-font-weight-semibold);
}
.cb-card-quiet p {
  font-size: var(--cb-type-xs-size);
}

/* Where you are, rather than only a way back. */
.cb-crumb {
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
  margin-bottom: var(--cb-space-lg);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}
.cb-crumb a {
  color: inherit;
}
.cb-crumb a:hover {
  color: var(--cbc-ink);
}
.cb-crumb > span::before {
  content: "/ ";
  color: var(--cbc-line-strong);
}

.cb-facts {
  display: grid;
  gap: var(--cb-space-xl);
}
.cb-fact {
  display: grid;
  gap: var(--cb-space-xxs);
}
.cb-fact dt {
  font-size: var(--cb-type-xs-size);
  text-transform: uppercase;
  letter-spacing: var(--cb-tracking-xxs);
  color: var(--cbc-ink-faint);
  font-weight: var(--cb-font-weight-semibold);
}
.cb-fact dd {
  margin: 0;
  font-size: var(--cb-type-sm-size);
  word-break: break-word;
}

.cb-timeline {
  display: grid;
  gap: var(--cb-space-md);
}
.cb-stage {
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-2xl);
  overflow: hidden;
}
.cb-stage > summary {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xl);
  padding: var(--cb-space-xl);
  cursor: pointer;
  font-size: var(--cb-type-sm-size);
  font-weight: var(--cb-font-weight-medium);
}
.cb-stage > summary::marker {
  color: var(--cbc-ink-faint);
}
/* The card said "open one to see what it found" and then gave the rows nothing
   that reads as openable. A chevron and a hover do. */
.cb-stage > summary::after {
  margin-inline-start: auto;
}
.cb-stage[open] > summary::after {
  transform: rotate(180deg);
}
.cb-stage > summary:hover {
  background: var(--cbc-sunken);
}
.cb-stage-name {
  flex: none;
}
/* The failure reason, on the row. A row saying only "Failed" makes you open it
   to learn anything at all, and this is the row you came for. */
.cb-stage-why {
  min-width: 0;
  color: var(--cbc-poor-text);
  font-weight: var(--cb-font-weight-regular);
}
.cb-stage-time {
  margin-inline-start: auto;
  color: var(--cbc-ink-faint);
  font-variant-numeric: tabular-nums;
}
.cb-stage-body {
  padding: 0 var(--cb-space-xl) var(--cb-space-xl);
}

/* Steps that have not run yet, drawn while a scan is live so the shape of the
   whole pipeline is visible rather than growing an item at a time. */
.cb-stage-waiting {
  border-style: dashed;
  opacity: 0.55;
}
.cb-stage-waiting[data-current] {
  opacity: 1;
  border-style: solid;
  border-color: var(--cbc-accent);
}
.cb-stage-line {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xl);
  padding: var(--cb-space-xl);
  font-size: var(--cb-type-sm-size);
  color: var(--cbc-ink-faint);
}
.cb-stage-waiting[data-current] .cb-stage-line {
  color: var(--cbc-ink);
}

/* This used to set background:--cbc-ink and color:--cbc-line, which is correct
   only in light mode. In dark mode --cbc-ink IS the light colour and --cbc-line
   is that same colour at 15% alpha, so the block rendered as a white rectangle
   with invisible text on it. Both roles now come from the surface scale, which
   means the same thing in both themes. */
.cb-code {
  margin: 0;
  padding: 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-family: var(--cb-font-mono);
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-type-sm-line-height);
  max-height: var(--cb-width-md);
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

.cb-shots {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--cb-width-xxs), 1fr));
  gap: var(--cb-space-xl);
}
.cb-shot {
  display: grid;
  gap: var(--cb-space-md);
}
.cb-shot img {
  width: 100%;
  border-radius: var(--cb-radius-xl);
  border: 1px solid var(--cbc-line);
  background: var(--cbc-sunken);
}
.cb-shot figcaption {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}

/* 1024px mirrors tokens.breakpoint.lg */
@media (max-width: 1024px) {
  .cb-detail-grid {
    grid-template-columns: minmax(0, 1fr);
  }
  /* Below here the columns genuinely do not fit, so the table keeps its own
     width and the region scrolls to it rather than squeezing seven columns into
     a phone. The region and the sticky header are both there already; this is
     only the floor under the table. */
  .cb-table-fixed {
    min-width: var(--cb-width-2xl);
  }
}

/* ---- Submission detail: the answer before the evidence ---- */

/* The score and the four facts that frame it, above everything the scan did to
   arrive at them. This block used to be a badge in the middle of a side rail,
   at the same weight as the document id. */
.cb-summary {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--cb-space-5xl);
  align-items: center;
  padding: var(--cb-space-4xl);
  margin-block-end: var(--cb-space-2xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-3xl);
  background: var(--cbc-surface);
}
/* Four across, dropping to two and then one. auto-fit on a 160px minimum put
   three on a row and the fourth underneath at ordinary desktop widths, which
   reads as a wrap rather than as a layout. */
.cb-summary-facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--cb-space-3xl) var(--cb-space-4xl);
}
/* 1024px mirrors tokens.breakpoint.lg */
@media (max-width: 1024px) {
  .cb-summary-facts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
/* 768px mirrors tokens.breakpoint.md */
@media (max-width: 768px) {
  .cb-summary-facts {
    grid-template-columns: minmax(0, 1fr);
  }
}
.cb-summary-tile {
  display: grid;
  gap: var(--cb-space-xs);
  align-content: start;
}
.cb-summary-label {
  font-size: var(--cb-type-xs-size);
  text-transform: uppercase;
  letter-spacing: var(--cb-tracking-xxs);
  font-weight: var(--cb-font-weight-semibold);
  color: var(--cbc-ink-faint);
}
.cb-summary-value {
  font-size: var(--cb-type-xl-size);
  font-weight: var(--cb-font-weight-semibold);
  line-height: var(--cb-type-xl-line-height);
  font-variant-numeric: tabular-nums;
}
.cb-summary-note {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-muted);
  line-height: var(--cb-type-sm-line-height);
}

/* The score as a shape rather than two numbers to subtract. The filled arc is
   what they scored; the lighter arc runs on to what the scan says is reachable,
   so the gap is the thing you see rather than something you work out. */
.cb-summary-score {
  display: flex;
  align-items: center;
  gap: var(--cb-space-2xl);
}
.cb-summary-dial {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: var(--cb-space-10xl);
  block-size: var(--cb-space-10xl);
  flex: none;
}
.cb-summary-ring {
  position: absolute;
  inset: 0;
  border-radius: var(--cb-radius-full);
  background: conic-gradient(
    var(--cb-ring-colour) calc(var(--cb-score) * 1%),
    var(--cb-ring-rest) calc(var(--cb-score) * 1%) calc(var(--cb-score-potential) * 1%),
    var(--cbc-line) calc(var(--cb-score-potential) * 1%)
  );
  /* The hole, so it reads as a ring rather than a pie. */
  mask: radial-gradient(
    farthest-side,
    transparent calc(100% - var(--cb-space-md)),
    var(--cb-color-black) calc(100% - var(--cb-space-md))
  );
}
.cb-summary-dial[data-tone="good"] {
  --cb-ring-colour: var(--cbc-good);
}
.cb-summary-dial[data-tone="warn"] {
  --cb-ring-colour: var(--cbc-warn);
}
.cb-summary-dial[data-tone="poor"] {
  --cb-ring-colour: var(--cbc-poor);
}
/* The stretch between the score and what is reachable. */
.cb-summary-dial {
  --cb-ring-rest: color-mix(in srgb, var(--cbc-good) 26%, transparent);
}
.cb-summary-score[data-empty] .cb-summary-big {
  color: var(--cbc-ink-faint);
}
.cb-summary-score-text {
  display: grid;
  gap: var(--cb-space-xs);
}
.cb-summary-big {
  font-family: var(--cb-font-heading);
  font-size: var(--cb-type-3xl-size);
  line-height: 1;
  font-weight: var(--cb-font-weight-bold);
  font-variant-numeric: tabular-nums;
}
.cb-summary-score .cb-summary-note {
  max-inline-size: var(--cb-space-11xl);
}

/* Who sent it, as a line. It was three rows of a definition list carrying one
   sentence of information. */
.cb-lead-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cb-space-lg);
  margin-block-end: var(--cb-space-3xl);
  font-size: var(--cb-type-sm-size);
}
.cb-lead-email {
  font-weight: var(--cb-font-weight-medium);
}

/* Money, as a ledger. It was a four-column table in a column a third of the
   page wide, where "vertex / gemini-3.1-flash-lite" wrapped over five lines;
   then it was a list that still led with a model id. The provider's own mark
   says which engine ran faster than any string does, and the bar says which
   call was the expensive one without anybody comparing figures. */
.cb-spend {
  display: grid;
  gap: var(--cb-space-xl);
}
.cb-spend-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--cb-space-lg);
  align-items: start;
}
.cb-spend-mark {
  inline-size: var(--cb-space-3xl);
  block-size: var(--cb-space-3xl);
  flex: none;
  color: var(--cbc-ink-muted);
  margin-block-start: var(--cb-space-xxs);
}
/* The base for this lives in console-charts.css, beside .cb-provider-mark which
   is its only sibling. The copy that was here contributed the background and the
   weight by loading later; both are in that one definition now. */
.cb-spend-what {
  min-width: 0;
  display: grid;
  gap: var(--cb-space-xs);
}
.cb-spend-stage {
  font-size: var(--cb-type-sm-size);
  font-weight: var(--cb-font-weight-medium);
  min-width: 0;
}
.cb-spend-engine {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cb-space-md);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}
.cb-spend-tokens {
  font-variant-numeric: tabular-nums;
}
.cb-spend-tokens::before,
.cb-spend-retry::before {
  content: "· ";
}
.cb-spend-retry {
  color: var(--cbc-warn-text);
}
.cb-spend-cost {
  font-size: var(--cb-type-sm-size);
  font-weight: var(--cb-font-weight-semibold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.cb-spend-cost[data-none] {
  font-weight: var(--cb-font-weight-regular);
  color: var(--cbc-ink-faint);
}
/* ---- Stage rows ---- */

/* A dot, not a badge. Twelve rows each opening with a green "OK" pill is the
   word OK twelve times; the exceptions are what the eye should find. */
.cb-stage-dot {
  flex: none;
  inline-size: var(--cb-space-sm);
  block-size: var(--cb-space-sm);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-ink-faint);
}
.cb-stage-dot[data-tone="good"] {
  background: var(--cbc-good);
}
.cb-stage-dot[data-tone="warn"] {
  background: var(--cbc-warn);
}
.cb-stage-dot[data-tone="poor"] {
  background: var(--cbc-poor);
}

.cb-stage-head {
  display: grid;
  gap: var(--cb-space-xxs);
  min-width: 0;
  margin-inline-end: auto;
}
/* What the step found, on the closed row. Twelve rows that each have to be
   opened before they say anything is a list nobody opens. */
/* Two lines, then ellipsis. Clipping to one line cut sentences mid-word
   ("Google's safet...") on rows that had the width to finish them. */
.cb-stage-found {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: var(--cb-type-sm-line-height);
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-regular);
  color: var(--cbc-ink-muted);
}
.cb-stage-found[data-tone="poor"] {
  color: var(--cbc-poor-text);
}
.cb-stage-flag {
  flex: none;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-warn-text);
}
.cb-stage-notes {
  display: grid;
  gap: var(--cb-space-sm);
  margin-block-start: var(--cb-space-lg);
  padding-inline-start: var(--cb-space-xl);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-muted);
  line-height: var(--cb-type-sm-line-height);
}
.cb-stage-body {
  display: grid;
  gap: var(--cb-space-lg);
}

/* The raw output, one fold further in than it used to be. It was the expanded
   state itself, so opening "crawl" showed four hundred sitemap timestamps. */
.cb-fold-tech {
  margin-block-start: var(--cb-space-sm);
}
.cb-fold-tech > summary {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}

/* A card that is folded shut: the identifiers, the raw document. */
.cb-card-fold > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cb-space-lg);
}
.cb-card-fold > summary::-webkit-details-marker {
  display: none;
}
.cb-card-fold > summary::after {
  margin-inline-start: auto;
}
.cb-card-fold[open] > summary::after {
  transform: rotate(180deg);
}
.cb-card-fold[open] > summary {
  margin-block-end: var(--cb-space-xl);
}

/* Chips that carry a verdict rather than a label. The base chip is a mono tag
   for ids and model names; these are sentences and read as such. */
.cb-chip[data-tone] {
  font-family: var(--cb-font-body);
  padding-inline: var(--cb-space-lg);
  gap: var(--cb-space-sm);
}
/* The dot carries the colour and the label stays ink, for the same reason the
   badges do: coloured small text on a tinted pill does not clear the contrast
   threshold, and this console is read all day by the people who sell
   accessibility work. */
.cb-chip[data-tone] {
  color: var(--cbc-ink);
}
.cb-chip[data-tone]::before {
  content: "";
  flex: none;
  inline-size: var(--cb-space-md);
  block-size: var(--cb-space-md);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-ink-faint);
}
.cb-chip[data-tone="good"] {
  background: color-mix(in srgb, var(--cbc-good) 12%, transparent);
}
.cb-chip[data-tone="good"]::before {
  background: var(--cbc-good);
}
.cb-chip[data-tone="warn"] {
  background: color-mix(in srgb, var(--cbc-warn) 16%, transparent);
}
.cb-chip[data-tone="warn"]::before {
  background: var(--cbc-warn);
}
.cb-chip[data-tone="poor"] {
  background: color-mix(in srgb, var(--cbc-poor) 10%, transparent);
}
.cb-chip[data-tone="poor"]::before {
  background: var(--cbc-poor);
}

/* 768px mirrors tokens.breakpoint.md */
@media (max-width: 768px) {
  .cb-summary {
    grid-template-columns: minmax(0, 1fr);
    justify-items: start;
  }
}

/* ---- The business dossier ----
   A two-column board rather than a stack. The score history and the scan list
   are the wide ones; who they are and what is wrong with their site sit beside
   each other, because they are read together: the address and the platform on
   one side, the categories that are failing on the other. */
.cb-dossier {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--cb-space-2xl);
  align-items: start;
}
.cb-dossier > .cb-card {
  margin: 0;
}
.cb-dossier > [data-span="wide"] {
  grid-column: 1 / -1;
}
/* The scan list is the last thing on the screen and the widest thing on it. */
.cb-dossier > .cb-card:last-child {
  grid-column: 1 / -1;
}

/* ---- A business, in a table cell ----
   The name over the domain, with the site's own mark. A business is a name to
   the person reading this and a domain to the tool, and the name is the half
   that gets recognised in a meeting. */
.cb-biz-cell {
  display: flex;
  align-items: center;
  gap: var(--cb-space-lg);
  min-width: 0;
}
.cb-biz-who {
  display: grid;
  gap: 1px;
  min-width: 0;
  line-height: var(--cb-type-sm-line-height);
}
.cb-biz-who .cb-cell-muted {
  font-size: var(--cb-type-xs-size);
}

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

/* ---- Drawn marks, retired ----
   Every chevron in this console is the lucide chevron, set as a mask so it
   takes currentColor and can be rotated the way a border-and-rotate trick
   allowed. A chevron built out of two borders renders at a different weight
   and optical centre from the lucide ones beside it, and there is no version
   of that which does not look like two products (docs/10). */
/* Not .cb-resize. Its mark is a corner bracket, drawn from --cbc-resize in
   console-charts.css, and this rule sits in a stylesheet that loads after that
   one at the same specificity: the chevron was quietly winning, so the handle
   for resizing a card was a downward chevron, which says nothing about resizing
   anything. Adding a selector to a shared list is how that happens. */
.cb-chevron,
.cb-stage > summary::after,
.cb-card-fold > summary::after,
.cb-pick-wrap::after,
.cb-expand > summary::before,
.cb-reveal::after {
  content: "";
  flex: none;
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
  background: currentColor;
  mask: var(--cbc-chevron) center / contain no-repeat;
  transition: transform var(--cb-motion-fast) var(--cbc-spring);
}

/* ---- The homepage, across the top of the card it describes ---- */

/* One look at the site says more about who is being called than the four rows
   under it, and the scan already took the picture. Cropped from the top: a
   full-page capture is mostly footer. */
.cb-cover {
  display: block;
  block-size: var(--cb-space-11xl);
  margin: calc(var(--cb-space-3xl) * -1) calc(var(--cb-space-3xl) * -1) var(--cb-space-2xl);
  overflow: hidden;
  background: var(--cbc-sunken);
  border-block-end: 1px solid var(--cbc-line);
}
.cb-cover img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  object-position: top center;
  display: block;
  transition: transform var(--cb-motion-slow) var(--cbc-spring);
}
.cb-cover:hover img {
  transform: scale(1.02);
}
.cb-cover[data-empty] {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--cb-space-md);
  block-size: var(--cb-space-10xl);
  color: var(--cbc-ink-faint);
  font-size: var(--cb-type-xs-size);
}
/* The card clips the cover's corners rather than the cover guessing them. */
.cb-card-covered {
  overflow: hidden;
}

/* ---- A fact with a mark, as a card rather than a row of a list ----
   Named facet, not tile: .cb-tile is the dashboard's big-number stat card in
   console-charts.css, and reusing it rendered a platform name at stat size,
   wrapping one letter per line. */
.cb-facets {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--cb-space-11xl), 1fr));
  gap: var(--cb-space-xl);
}
.cb-facet {
  display: flex;
  gap: var(--cb-space-lg);
  padding: var(--cb-space-xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-xl);
  background: var(--cbc-sunken);
}
.cb-facet-glyph {
  color: var(--cbc-ink-faint);
  flex: none;
}
.cb-facet-text {
  display: grid;
  gap: var(--cb-space-xxs);
  min-width: 0;
}
.cb-facet-label {
  font-size: var(--cb-type-xs-size);
  text-transform: uppercase;
  letter-spacing: var(--cb-tracking-xxs);
  color: var(--cbc-ink-faint);
  font-weight: var(--cb-font-weight-semibold);
}
.cb-facet-value {
  font-size: var(--cb-type-sm-size);
  font-weight: var(--cb-font-weight-medium);
  word-break: break-word;
}
.cb-facet-note {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-muted);
  line-height: var(--cb-type-sm-line-height);
}

/* ---- Add-ons, grouped by what they do ---- */
.cb-groups {
  display: grid;
  gap: var(--cb-space-xl);
}
.cb-group {
  display: grid;
  gap: var(--cb-space-md);
}
.cb-group-label {
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
  font-size: var(--cb-type-xs-size);
  text-transform: uppercase;
  letter-spacing: var(--cb-tracking-xxs);
  font-weight: var(--cb-font-weight-semibold);
  color: var(--cbc-ink-faint);
}
/* A version, quieter than the name it belongs to. */
.cb-chip-meta {
  font-family: var(--cb-font-mono);
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}

/* A mark beside a label, wherever a label carries one. */
.cb-fact dt,
.cb-row-title:has(.cb-icon) {
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
}
.cb-fact dt .cb-icon,
.cb-row-title .cb-icon {
  color: var(--cbc-ink-faint);
}
/* The stage rows: the mark sits with the dot, not with the title. */
.cb-stage-glyph {
  flex: none;
  color: var(--cbc-ink-faint);
}

/* ---- The model catalogue ----
   A comparison, not a printout. Four columns instead of six: the provider is a
   band over the models that belong to it rather than a word repeated down a
   column, and the tier is a chip on the name it describes rather than a short
   sentence in a cell of its own.

   The two columns anyone opens this screen for are the two the old table made
   hardest to compare. What a model can do was the same two words on nearly
   every row; it is two marks now, lit or not. And a price printed as a figure
   says nothing about the dearest model here being twelve times the cheapest,
   so each figure keeps a bar drawn against the top of the list. */
.cb-cat-band > th {
  padding: var(--cb-space-lg) var(--cb-space-3xl);
  background: var(--cbc-sunken);
  border-block: 1px solid var(--cbc-line);
  letter-spacing: normal;
  text-transform: none;
}
.cb-cat-band-row {
  display: flex;
  align-items: center;
  gap: var(--cb-space-lg);
}
.cb-cat-mark {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}
.cb-cat-provider {
  font-size: var(--cb-type-sm-size);
  font-weight: var(--cb-font-weight-semibold);
  color: var(--cbc-ink);
}
.cb-cat-count {
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-regular);
  color: var(--cbc-ink-faint);
}
/* Kept, because old audits are priced from them. Folded away, because nobody
   is choosing one. */
.cb-cat-toggle {
  margin-inline-start: auto;
  padding: var(--cb-space-xxs) var(--cb-space-lg);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-full);
  background: none;
  color: var(--cbc-ink-faint);
  font: inherit;
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-regular);
  cursor: pointer;
  transition: color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-cat-toggle:hover {
  color: var(--cbc-ink);
}
.cb-cat-toggle[aria-expanded="true"] {
  color: var(--cbc-ink);
  background: var(--cbc-hover);
}
.cb-cat-old > tr {
  animation: cbc-emerge var(--cb-motion-base) var(--cbc-spring) backwards;
}
.cb-cat tbody tr[data-retired] .cb-cell-strong {
  color: var(--cbc-ink-muted);
}

.cb-cat-title {
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
}
.cb-cat td {
  vertical-align: middle;
}

/* ---- What the daily check has to say ----
   Two marks and one line, and all three are exceptions. The rule (checked
   this morning, figures unchanged) is stated once above the table, because a
   column repeating today's date on every row is ninety rows of nothing. */
.cb-cat-checked {
  font-variant-numeric: tabular-nums;
}
.cb-cat-checked[data-tone="warn"] {
  color: var(--cbc-warn-text);
}
.cb-cat-sunset {
  display: grid;
  place-items: center;
  color: var(--cbc-warn-text);
}
.cb-cat-sunset .cb-icon {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}
.cb-cat-held {
  display: grid;
  place-items: center;
  color: var(--cbc-ink-muted);
}
.cb-cat-held .cb-icon {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}
.cb-cat-unconfirmed {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-warn-text);
}

/* Lit or not, with the reason in a tooltip. Two marks say what "images, web
   search" said on seven rows out of eight. */
.cb-cat-can {
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
}
.cb-cat-mark-slot {
  display: grid;
  place-items: center;
  color: var(--cbc-ink-faint);
}
.cb-cat-mark-slot .cb-icon {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}
.cb-cat-mark-slot[data-on="false"] {
  opacity: 0.25;
}
.cb-cat-mark-slot[data-on="true"] {
  color: var(--cbc-ink);
}

/* Three steps, filled for the levels it takes. A comma-separated list of the
   same three words has to be read; this is seen. */
.cb-cat-think {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xs);
}
.cb-cat-step {
  inline-size: var(--cb-space-lg);
  block-size: var(--cb-space-sm);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-line-strong);
}
.cb-cat-step[data-on="true"] {
  background: var(--cbc-ink-muted);
}
.cb-cat-think[data-none="true"] .cb-cat-step {
  opacity: 0.4;
}

/* ---- What it costs ----
   The figure, and the ratio behind it. Scaled to the dearest live model, so
   the column reads as a ladder rather than as eight unrelated numbers. */
.cb-cat-price {
  display: grid;
  gap: var(--cb-space-xs);
  justify-items: end;
}
.cb-cat-line {
  display: grid;
  grid-template-columns: var(--cb-space-9xl) var(--cb-space-8xl);
  align-items: center;
  gap: var(--cb-space-lg);
}
.cb-cat-bar {
  position: relative;
  block-size: var(--cb-space-sm);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-line);
  overflow: hidden;
}
.cb-cat-bar::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: var(--share, 0%);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-ink-muted);
  animation: cbc-wipe-x var(--cb-motion-slow) var(--cbc-spring) backwards;
}
/* The output line is the one that runs away with a bill, so it takes the ink. */
.cb-cat-line[data-which="out"] .cb-cat-bar::after {
  background: var(--cbc-accent);
}
.cb-cat-figure {
  font-variant-numeric: tabular-nums;
  font-size: var(--cb-type-sm-size);
  text-align: end;
}
.cb-cat-line[data-which="in"] .cb-cat-figure {
  color: var(--cbc-ink-muted);
}

/* 1024px mirrors tokens.breakpoint.lg */
@media (max-width: 1024px) {
  .cb-cat-line {
    grid-template-columns: var(--cb-space-7xl) var(--cb-space-8xl);
  }
}

/* ---- The quiet icon button on a row ----
   WHAT IS LEFT OF THE TEAM ROW, and the only part of it worth keeping. The
   five-column .cb-member grid that used to live here is gone: it could not fit
   its own contents, crushed an invited address to a single letter, and sized
   its "last seen" track with max-content so the dates never lined up. The Team
   screen is a cluster, a card and a list now (console-team.css).

   This one class survives because it was never the team screen's alone:
   profile-passkeys.ts wears it for the same job, a small quiet control that
   appears at the end of a row. */
/* A button and a link side by side, so the button has to be told it is not a
   button: the browser's default border was drawing a ring round the pencil and
   not round the clock beside it. */
.cb-member-history {
  display: grid;
  place-items: center;
  border: 0;
  padding: 0;
  background: none;
  cursor: pointer;
  inline-size: var(--cb-space-4xl);
  block-size: var(--cb-space-4xl);
  border-radius: var(--cb-radius-full);
  color: var(--cbc-ink-faint);
  text-decoration: none;
  transition:
    color var(--cb-motion-fast) var(--cbc-spring),
    background var(--cb-motion-fast) var(--cbc-spring);
}
.cb-member-history:hover {
  color: var(--cbc-ink);
  background: var(--cbc-hover);
}
.cb-member-history .cb-icon {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}

/* Whether there is a code app on the account. It sits in the actions strip and
   is deliberately NOT one of them: no hover, no pointer, no hit area. Something
   that answers to the pointer and then does nothing is worse than something
   that plainly never offered. */

/* ---- Asking for a bigger seat ----
   THE WHOLE LADDER, AND THE RUNG THEY ARE ON. It was two bordered boxes holding
   two words, which is a menu: it could tell somebody what an Editor is and not
   where they were standing, and "how much further than where I am" is the only
   question anybody actually has here. So all three rungs are drawn, weakest
   first, and the one they hold sits quietly at the bottom of them.

   Cards rather than a select, because what each seat CAN DO is the decision and
   a menu hides that behind the part that is only a word. */
.cb-ask-seats {
  display: grid;
  gap: var(--cb-space-md);
}
.cb-ask-seat {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--cb-space-lg);
  text-align: start;
  inline-size: 100%;
  padding: var(--cb-space-lg) var(--cb-space-xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-xl);
  background: none;
  cursor: pointer;
  color: inherit;
  transition:
    border-color var(--cb-motion-fast) var(--cbc-spring),
    background var(--cb-motion-fast) var(--cbc-spring);
}
.cb-ask-seat:hover {
  background: var(--cbc-hover);
  border-color: var(--cbc-line-strong);
}
.cb-ask-seat:focus-visible {
  outline: var(--cbc-focus);
  outline-offset: var(--cbc-focus-gap);
}
/* Chosen, in the console's own words for it: the row fills, the border goes to
   the accent, and the mark at the head of it turns into a tick. The same
   picture the onboarding cards draw, because it is the same fact. */
.cb-ask-seat[data-on] {
  border-color: var(--cbc-accent);
  background: var(--cbc-accent-wash);
}
.cb-ask-seat-said {
  display: grid;
  gap: var(--cb-space-xxs);
  min-inline-size: 0;
}
.cb-ask-seat-said > span {
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-type-sm-line-height);
  color: var(--cbc-ink-faint);
}

/* The mark, in the slot a number sits in on the console's other choice rows.
   It holds two glyphs in one place and shows whichever the state calls for, so
   the row does not change width when it is chosen. */
.cb-ask-seat-mark {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: var(--cb-space-5xl);
  block-size: var(--cb-space-5xl);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-lg);
  background: var(--cbc-sunken);
  color: var(--cbc-ink-faint);
  transition:
    background var(--cb-motion-fast) var(--cbc-spring),
    border-color var(--cb-motion-fast) var(--cbc-spring),
    color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-ask-seat-mark > * {
  grid-area: 1 / 1;
  transition: opacity var(--cb-motion-fast) var(--cb-motion-ease);
}
.cb-ask-seat-tick {
  opacity: 0;
}
.cb-ask-seat:hover .cb-ask-seat-mark {
  border-color: var(--cbc-line-strong);
  color: var(--cbc-ink);
}
.cb-ask-seat[data-on] .cb-ask-seat-mark {
  background: var(--cbc-accent);
  border-color: var(--cbc-accent);
  color: var(--cbc-on-accent);
}
.cb-ask-seat[data-on] .cb-ask-seat-tick {
  opacity: 1;
}
.cb-ask-seat[data-on] .cb-ask-seat-mark > :not(.cb-ask-seat-tick) {
  opacity: 0;
}

/* The rung they are standing on. Turned off rather than left out (rule 15), and
   it reads as a fact rather than as a refused choice: no hover, no pointer, and
   a chip beside it saying what it is. */
.cb-ask-seat[data-now] {
  cursor: default;
}
.cb-ask-seat[data-now]:hover {
  background: none;
  border-color: var(--cbc-line);
}
.cb-ask-seat[data-now] .cb-ask-seat-said > strong,
.cb-ask-seat[data-now] .cb-ask-seat-said > span {
  color: var(--cbc-ink-faint);
}
.cb-ask-seat-now {
  padding: var(--cb-space-xxs) var(--cb-space-md);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-sunken);
  color: var(--cbc-ink-muted);
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-medium);
  white-space: nowrap;
}

/* ================================================================
   The three lists: submissions, businesses, the activity trail
   ================================================================
   They are the same screen with different columns, so they are built from the
   same pieces: a summary strip that is also the filter, a toolbar, named
   views, a count, and the list itself. A person moving between them should not
   have to relearn where anything is.

   Everything from here to the trail's own rows is shared by all three. */

/* ---- The summary strip: what is in here, before a row is read ---- */
/* ---- THE TOP OF A LIST SCREEN ----

   It was three white cards stacked: a two hundred and forty pixel summary, a
   hundred and fifty pixel box holding a search field, and then the table. Six
   hundred pixels of chrome before one row of data, on a page whose whole job is
   the rows. Now it is a strip, a bar and the table, and the only thing raised
   off the page is the table.

   THE STRIP TAKES THE PAGE'S OWN GROUND. No surface, no border, no shadow: a
   summary belongs to the page rather than sitting on it, and a card around four
   numbers was a box drawn for the sake of drawing one. */
/* AIR BETWEEN THE READINGS AND THE SPLIT. Eight pixels put four horizontal
   bars in a stack with nothing between them: three meters and the split bar
   read as one set of stripes, and which meter belonged to which figure was
   something you had to work out. They are two different measurements of the
   same list and they are spaced like it now. */
.cb-funnel {
  display: grid;
  gap: var(--cb-space-2xl);
  padding: 0 var(--cb-space-xs) var(--cb-space-lg);
  border-bottom: 1px solid var(--cbc-line);
}
/* The same air on the trail's strip, which is the same two things without the
   meters: three figures, then the split they are drawn from. It is a block
   rather than a grid, so the gap goes on the part that has to move. */
.cb-spread > .cb-funnel-split {
  margin-block-start: var(--cb-space-2xl);
}
.cb-funnel-steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--cb-space-11xl), 1fr));
  /* Tighter than it was: twenty-four pixels of air was what separated three
     columns of bare type, and they have edges of their own now. */
  gap: var(--cb-space-lg);
}
/* A LOWKEY CARD PER READING. They were three columns of bare type sharing one
   background, so a meter sat between the figure above it and the sentence below
   it with nothing saying which of the two it belonged to, and the split bar
   under all three read as a fourth meter in the same set.

   The faintest fill in the palette and a hairline, no shadow: this is a strip
   inside a page, and a raised card here would compete with the table below,
   which is the thing being read. `--cbc-hover` rather than `--cbc-surface`
   because the strip sits on the page's own ground, and the surface colour would
   make three cards on a page of cards. */
.cb-funnel-step {
  /* Positioned and clipped, because the intake step carries a sparkline and it
     places itself against its own tile (console-charts.css). */
  position: relative;
  overflow: hidden;
  display: grid;
  gap: var(--cb-space-xs);
  align-content: start;
  padding: var(--cb-space-md) var(--cb-space-lg);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-xl);
  background: var(--cbc-hover);
  animation: cbc-rise var(--cb-motion-slow) var(--cbc-spring) backwards;
  animation-delay: var(--cbc-inside);
}
.cb-funnel-step[data-unmeasured] {
  opacity: 0.55;
}
/* The figure, its name and its share on one line, reading left to right in the
   order somebody says them: "a hundred and thirty three submitted". */
/* The name, over the figure. Same eyebrow as the trail's tiles and the
   dashboard's stat cards (.cb-spread-unit), because it is the same thing. */
.cb-funnel-label {
  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);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-funnel-head {
  display: flex;
  align-items: baseline;
  gap: var(--cb-space-md);
  margin: 0;
  min-inline-size: 0;
}
.cb-funnel-n {
  font-size: var(--cb-type-2xl-size);
  line-height: var(--cb-type-2xl-line-height);
  letter-spacing: var(--cb-type-2xl-letter-spacing);
  font-weight: var(--cb-font-weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--cbc-ink);
}
/* The share, in the chip the rest of the console puts a direction in: a small
   qualification riding the figure's baseline rather than a number pushed to the
   far end of a line it has nothing to do with. Neutral, because a share of a
   funnel is neither good news nor bad on its own. */
.cb-funnel-share {
  padding: var(--cb-space-xxs) var(--cb-space-md);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-sunken);
  font-size: var(--cb-type-xs-size);
  font-variant-numeric: tabular-nums;
  color: var(--cbc-ink-muted);
}
/* WHAT MAKES FOUR FIGURES A SHAPE. A meter under each step, filled by its share
   of the intake, so "eight out of a hundred and thirty three" is seen rather
   than worked out. Four steps in the funnel's own four colours, the same four
   the dashboard card uses for the same four stages. */
.cb-funnel-meter {
  block-size: var(--cb-space-xs);
  /* Under the line it qualifies now, the way the trail's tiles wear theirs. */
  margin-block-start: var(--cb-space-xs);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-sunken);
  overflow: hidden;
}
.cb-funnel-meter i {
  display: block;
  block-size: 100%;
  inline-size: calc(var(--at) * 100%);
  border-radius: inherit;
  background: var(--cbc-flow-in);
  transition: inline-size var(--cb-motion-slow) var(--cbc-spring);
}
.cb-funnel-step[data-at="1"] .cb-funnel-meter i {
  background: var(--cbc-flow-mid);
}
.cb-funnel-step[data-at="2"] .cb-funnel-meter i {
  background: var(--cbc-flow-warm);
}
.cb-funnel-step[data-at="3"] .cb-funnel-meter i {
  background: var(--cbc-flow-best);
}
.cb-funnel-lost {
  margin: 0;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* THE FIGURE IS INK, NOT THE TINT. The bar above already carries this step's
   colour as a background, which is the direction the tint set is guaranteed in
   (blackcurrant on the tint, above 5:1, category-tint.test.ts). Used as ink the
   same tint is 2.31:1 on the light theme's white, so the one number on this line
   was the least readable thing on it while the colour it borrowed was already
   being shown an inch away. Ink also does the job the tint was doing: it stands
   out from the --cbc-ink-faint sentence around it. */
.cb-funnel-lost b {
  color: var(--cbc-ink);
  font-variant-numeric: tabular-nums;
}
/* THE CUT: the bar, and its legend on the line under it.
   Side by side is what this was, and it worked while a list had four or five
   areas: past that the chips took most of the line and the bar they describe
   was squeezed into whatever was left, or wrapped to a second row of chips
   beside a bar an inch wide. A stack instead, on every list screen, so the bar
   is always the full width of the thing it is a picture of and the legend
   always has the whole line to wrap in. */
.cb-funnel-split {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--cb-space-lg);
  min-inline-size: 0;
}
.cb-funnel-split .cb-spread-bar {
  margin: 0;
}
@media (prefers-reduced-motion: reduce) {
  .cb-funnel-meter i {
    transition: none;
  }
}

/* ---- THE BAR THE LIST IS DRIVEN FROM (list-toolbar.ts) ----

   ONE VISUAL WEIGHT, and that is the whole of this. The first version of this
   row had four: a group of outlined pills, a white rounded search field the
   size of a card, two more white rounded fields for the menus, and a line of
   bare text. Four shapes, three heights and two radii in a strip forty pixels
   tall, which reads as a pile of controls rather than as a toolbar.

   So: the views are text, the way tabs are text, with the one that is on
   underlined. Everything that narrows the list is a quiet control on the page's
   own ground, and they sit together at the far end because they do the same
   job. Nothing here is filled or outlined until a pointer is on it. The only
   colour in the row is the view you are looking at.

   `cb-listbar`, AND THE NAME TOOK THREE GOES: `cb-bar` was already the scoring
   screen's weight bar and `cb-tools` a grid somewhere else, so this toolbar
   came out first 320 pixels wide and sixteen tall, then as three stacked rows.
   In a stylesheet this size the obvious name is usually taken. */
.cb-listbar {
  display: grid;
  /* Three tracks, not a wrapping flex row. The views are as wide as they are,
     the count is as wide as it is, the rest takes what is left. As flex this
     lost by one pixel and dropped the count onto a second line. */
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--cb-space-2xl);
  padding-block: var(--cb-space-md);
}

/* ---- Which list: tabs, not pills ---- */
.cb-listbar-views {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xl);
  min-inline-size: 0;
}
.cb-listbar-views .cb-view {
  position: relative;
  padding: var(--cb-space-xs) 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--cbc-ink-faint);
  white-space: nowrap;
}
.cb-listbar-views .cb-view:hover {
  color: var(--cbc-ink);
}
.cb-listbar-views .cb-view[aria-pressed="true"] {
  color: var(--cbc-ink);
}
/* The mark under the one you are looking at. Drawn on the tab rather than as a
   sliding rail, because a rail that has to keep up with a wrapping row is a
   thing to maintain and this is one line of CSS. */
.cb-listbar-views .cb-view[aria-pressed="true"]::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: calc(-1 * var(--cb-space-xs));
  block-size: 2px; /* an underline, not a spacing step */
  border-radius: var(--cb-radius-full);
  background: var(--cbc-accent);
}

/* ---- Everything that narrows the list, together at the far end ---- */
.cb-listbar > .cb-filters {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: nowrap;
  gap: var(--cb-space-md);
  min-inline-size: 0;
  padding: 0;
}
/* The search field: a glyph and a line, on the page's ground. It was a white
   card the width of its own placeholder sentence. */
.cb-seek {
  display: inline-flex;
  align-items: center;
  gap: var(--cb-space-sm);
  padding: var(--cb-space-xxs) var(--cb-space-md);
  border: 1px solid transparent; /* a hairline, held so focus cannot move it */
  border-radius: var(--cb-radius-md);
  background: var(--cbc-sunken);
  color: var(--cbc-ink-faint);
  transition: border-color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-seek:focus-within {
  border-color: var(--cbc-accent);
  color: var(--cbc-ink);
}
.cb-seek .cb-icon {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
  flex: none;
}
/* SCOPED, because console-forms.css loads after this file: `.cb-input` and
   `.cb-seek-field` are both one class, the later sheet wins, and the field kept
   its white card, its border and its radius INSIDE the pill that was supposed
   to be the control. Two classes settles it without an important. */
.cb-listbar .cb-seek-field {
  inline-size: var(--cb-space-10xl);
  padding: var(--cb-space-xs) 0;
  border: 0;
  border-radius: 0;
  background: none;
  transition: inline-size var(--cb-motion-fast) var(--cbc-spring);
}
.cb-listbar .cb-seek-field:hover {
  border: 0;
}
.cb-listbar .cb-seek-field:focus {
  outline: none;
  inline-size: var(--cb-width-xxs);
}
.cb-listbar .cb-seek-field::-webkit-search-cancel-button {
  display: none;
}
/* The menus: a word and a chevron, filled only under the pointer. Two outlined
   white fields for "Every status" and "All time" were the heaviest things in a
   row whose subject is somewhere else entirely. */
.cb-listbar > .cb-filters > .cb-pick-wrap {
  flex: none;
}
.cb-listbar .cb-pick,
.cb-listbar .cb-select {
  padding: var(--cb-space-sm) var(--cb-space-3xl) var(--cb-space-sm) var(--cb-space-md);
  border-color: transparent;
  border-radius: var(--cb-radius-md);
  background: none;
  color: var(--cbc-ink-faint);
}
.cb-listbar .cb-pick:hover:not(:disabled),
.cb-listbar .cb-select:hover {
  border-color: transparent;
  background: var(--cbc-hover);
  color: var(--cbc-ink);
}
.cb-listbar .cb-pick-wrap::after {
  inset-inline-end: var(--cb-space-md);
}
.cb-listbar .cb-filters-clear {
  margin: 0;
}
/* How many that leaves, at the end of the row it is the answer to. The figure
   in ink and what it is a share of beside it: one faint sentence read as a
   note somebody had left rather than as the count. */
.cb-listbar > .cb-count {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xs);
  padding: 0;
  justify-self: end;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.cb-count-said b {
  color: var(--cbc-ink);
  font-weight: var(--cb-font-weight-semibold);
}
/* The way out of a set of filters: a glyph at the end of the controls it
   clears, in their own weight. A worded ghost button beside two borderless
   menus was the one thing in the row shouting, and the least used of the four. */
.cb-listbar .cb-filters-clear {
  display: inline-flex;
  padding: var(--cb-space-sm);
  border: 0;
  border-radius: var(--cb-radius-md);
  background: none;
  color: var(--cbc-ink-faint);
  cursor: pointer;
  transition: background var(--cb-motion-fast) var(--cbc-spring);
}
.cb-listbar .cb-filters-clear:hover {
  background: var(--cbc-hover);
  color: var(--cbc-accent);
}
.cb-listbar .cb-filters-clear .cb-icon {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}

/* ---- THE PAGER (views/list-pager.ts) ----
   Where you are on the left, the pages in the middle, how big a page is on the
   right. It replaced a bar reading "50 of 133" with a "Load 83 more" button
   that pressed itself as you neared the bottom: fine for a feed, wrong for a
   list somebody works, where there is no way back to a place and no way to
   send a colleague one.

   ATTACHED TO THE TABLE, hairline above it, on the card's own ground. It is
   the bottom of the card, so it carries the card's bottom corners on the one
   screen where something follows the table. */
.cb-pager {
  display: grid;
  /* Three tracks so the numbers are centred on the TABLE rather than on
     whatever the two ends happen to be wide. As flex the row drifted sideways
     every time the range changed from "1–25" to "101–125". */
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--cb-space-lg);
  padding: var(--cb-space-lg) var(--cb-space-3xl) var(--cb-space-xs);
  border-top: 1px solid var(--cbc-line);
  border-end-start-radius: var(--cb-radius-3xl);
  border-end-end-radius: var(--cb-radius-3xl);
}
.cb-pager-said {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xs);
  margin: 0;
  min-inline-size: 0;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  font-variant-numeric: tabular-nums;
}
/* The figure in ink and what it is a share of beside it: the number is the
   thing being read, and a whole faint sentence hides it. */
.cb-pager-range b {
  color: var(--cbc-ink);
  font-weight: var(--cb-font-weight-semibold);
}
.cb-pager-moves {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xs);
}
.cb-pager-numbers {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xxs);
}
/* The four moves and the numbers are the same control at the same size, so the
   row reads as one thing rather than as arrows with a list between them. */
.cb-pager-step,
.cb-pager-n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: var(--cb-space-4xl);
  block-size: var(--cb-space-4xl);
  padding-inline: var(--cb-space-sm);
  border: 0;
  border-radius: var(--cb-radius-md);
  background: none;
  color: var(--cbc-ink-muted);
  font: inherit;
  font-size: var(--cb-type-sm-size);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition:
    background var(--cb-motion-fast) var(--cbc-spring),
    color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-pager-step:hover,
.cb-pager-n:hover {
  background: var(--cbc-hover);
  color: var(--cbc-ink);
}
/* WHERE YOU ARE IS A STATE, NOT A DESTINATION. Filled rather than outlined,
   because an outline on one of eight numbers reads as a focus ring on the wrong
   one, and it carries `aria-current` so it is not offered as somewhere to go. */
.cb-pager-n[aria-current="page"] {
  background: var(--cbc-ink);
  color: var(--cbc-surface);
  font-weight: var(--cb-font-weight-semibold);
}
.cb-pager-n[aria-current="page"]:hover {
  background: var(--cbc-ink);
  color: var(--cbc-surface);
}
.cb-pager-step[aria-disabled="true"] {
  opacity: 0.35;
  cursor: not-allowed;
}
.cb-pager-step[aria-disabled="true"]:hover {
  background: none;
  color: var(--cbc-ink-muted);
}
.cb-pager-step .cb-icon {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}
/* The space where the pages nobody asked for are. It keeps a number's width so
   the row does not close up around it. */
.cb-pager-gap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: var(--cb-space-4xl);
  block-size: var(--cb-space-4xl);
  color: var(--cbc-ink-faint);
}
.cb-pager-size {
  display: flex;
  justify-content: flex-end;
  min-inline-size: 0;
}
/* The same borderless menu the toolbar's narrowings wear: this is furniture
   about the list rather than a field to fill in. */
.cb-pager-size .cb-pick {
  border: 0;
  background: none;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}
.cb-pager-size .cb-pick:hover {
  color: var(--cbc-ink);
}
/* Held while a page is being fetched, so nothing under the pointer answers a
   press that is already on its way. */
.cb-pager[data-busy] .cb-pager-n {
  pointer-events: none;
  opacity: 0.5;
}
@media (prefers-reduced-motion: reduce) {
  .cb-pager-step,
  .cb-pager-n {
    transition: none;
  }
}
/* Under the phone breakpoint (--cb-width-sm) the three tracks stack: the range
   and the page size sit either side of a row of numbers that has the width to
   itself. */
@media (max-width: 480px) {
  .cb-pager {
    grid-template-columns: 1fr auto;
  }
  .cb-pager-moves {
    grid-column: 1 / -1;
    grid-row: 1;
    justify-content: center;
  }
}

/* ---- THE SELECTION TOOLBAR (submissions-actions.ts) ----
   The notifications panel's pick bar, on a table: inverted so it reads as a
   thing that arrived for one job, floating over the bottom of the list where
   the picking is happening, and icon-only so it is a row of taps rather than a
   sentence of buttons. It used to be a pale band wedged above the table that
   pushed every row down the moment a box was ticked. */
/* The slot the screen swaps the bar in and out of, taken out of the layout so
   the bar's parent IS the card. Sticky travels inside its own parent's box, and
   against a wrapper the size of the bar there is nowhere to travel: it simply
   never appeared. Against the card it has the whole table to move over. */
.cb-picked-slot {
  display: contents;
}
.cb-picked {
  /* Sticky rather than fixed, so it centres on the TABLE rather than on the
     window: fixed put it a hundred and twenty pixels left of the list it acts
     on, because half the window is not the middle of the content beside a rail. */
  position: sticky;
  inset-block-end: var(--cb-space-3xl);
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cb-space-2xl);
  inline-size: max-content;
  max-inline-size: calc(100% - var(--cb-space-6xl));
  margin-inline: auto;
  padding: var(--cb-space-xs) var(--cb-space-sm);
  border-radius: var(--cb-radius-full);
  /* The ink ground with the surface as its marks, which inverts cleanly in both
     themes without a second palette. */
  background: var(--cbc-ink);
  color: var(--cbc-surface);
  box-shadow: var(--cbc-shadow);
  animation: cbc-bar-rise var(--cb-motion-base) var(--cbc-spring) backwards;
}
.cb-picked-lead {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xs);
  min-inline-size: 0;
}
.cb-picked-lead p {
  margin: 0;
  padding-inline-end: var(--cb-space-sm);
  font-size: var(--cb-type-xs-size);
  white-space: nowrap;
}
.cb-picked-acts {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xxs);
}
.cb-picked-btn {
  display: inline-flex;
  padding: var(--cb-space-sm);
  border: 0;
  border-radius: var(--cb-radius-full);
  background: none;
  color: inherit;
  cursor: pointer;
  transition: background var(--cb-motion-fast) var(--cbc-spring);
}
.cb-picked-btn:hover {
  background: color-mix(in srgb, var(--cbc-surface) 18%, transparent);
}
.cb-picked-btn[aria-disabled="true"] {
  opacity: 0.4;
  cursor: not-allowed;
}
.cb-picked-btn[data-danger]:hover {
  background: color-mix(in srgb, var(--cbc-poor) 40%, transparent);
}
.cb-picked-btn .cb-icon {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}
@media (prefers-reduced-motion: reduce) {
  .cb-picked {
    animation: none;
  }
  .cb-listbar .cb-filters-clear,
  .cb-picked-btn {
    transition: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .cb-seek,
  .cb-seek-field {
    transition: none;
  }
}

/* The trail's three readings, in the same lowkey cards the funnel's steps wear
   (.cb-funnel-step above says why). A grid rather than a wrapping row, so the
   three are the same width as each other and line up with the split bar under
   them instead of ending wherever their own words end. */
.cb-spread-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--cb-space-11xl), 1fr));
  gap: var(--cb-space-lg);
}
/* A TILE, IN THE CONSOLE'S OWN TILE LANGUAGE (the dashboard's stat cards): the
   name over the figure, the figure with its direction beside it, a line saying
   something else true, and the shape of it underneath. It was a name and a
   number on one baseline, which is a sentence in a box.

   Positioned, because the sparkline places itself against its tile
   (console-charts.css) and clips to it. */
.cb-spread-fact {
  position: relative;
  overflow: hidden;
  display: grid;
  gap: var(--cb-space-xs);
  align-content: start;
  padding: var(--cb-space-md) var(--cb-space-lg);
  border: 1px solid var(--cbc-line);
  border-radius: var(--cb-radius-xl);
  background: var(--cbc-hover);
}
.cb-spread-unit {
  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);
}
/* The figure and its chip on one line, the chip riding the figure's baseline
   rather than its box: an arrow hanging off the top of a thirty-pixel number
   reads as a second heading. */
.cb-spread-line {
  display: flex;
  align-items: baseline;
  gap: var(--cb-space-md);
  margin: 0;
  min-inline-size: 0;
}
.cb-spread-figure {
  font-size: var(--cb-type-2xl-size);
  line-height: var(--cb-type-2xl-line-height);
  letter-spacing: var(--cb-type-2xl-letter-spacing);
  font-weight: var(--cb-font-weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--cbc-ink);
}
.cb-spread-note {
  margin: 0;
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The meter a funnel step wears, for the two tiles whose figure is part of
   something larger. Above the fold of the tile rather than behind it, because
   unlike the chart it IS the reading rather than the setting for one. */
.cb-spread-meter {
  block-size: var(--cb-space-xs);
  margin-block-start: var(--cb-space-xs);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-sunken);
  overflow: hidden;
}
.cb-spread-meter i {
  display: block;
  block-size: 100%;
  inline-size: calc(var(--at) * 100%);
  border-radius: inherit;
  /* The tint of whatever the share is about, falling back to the ink rather
     than to the accent: the accent means "press this" everywhere else. */
  background: var(--tint, var(--cbc-ink-faint));
  transition: inline-size var(--cb-motion-slow) var(--cbc-spring);
}
/* Shorter than the dashboard's, which has a whole card to sit in. Both shapes
   of the strip's tile, so the trail's and the funnel's charts are the same
   height as each other. */
.cb-spread-fact .cb-spark,
.cb-funnel-step .cb-spark {
  block-size: var(--cb-space-5xl);
}
@media (prefers-reduced-motion: reduce) {
  .cb-spread-meter i {
    transition: none;
  }
}

/* One bar, split by area. Segments grow by their own count rather than by a
   percentage, so the gaps between them come out of the whole instead of
   pushing the last one past the end. */
.cb-spread-bar {
  display: flex;
  gap: var(--cb-space-xxs);
  block-size: var(--cb-space-lg);
  /* Grows out from the left on arrival, once, with the card already there. */
  transform-origin: left center;
}
.cb-spread-piece {
  flex: var(--grow) 1 0;
  min-inline-size: var(--cb-space-md);
  padding: 0;
  border: 0;
  border-radius: var(--cb-radius-full);
  background: var(--tint);
  cursor: pointer;
  transition:
    opacity var(--cb-motion-base) var(--cbc-spring),
    scale var(--cb-motion-fast) var(--cbc-spring);
}
/* Picking one area holds the others back rather than removing them: the share
   you chose keeps its colour and you can still see how much of the whole it
   is (docs/09 on highlighting). */
.cb-spread-piece[data-lit="false"] {
  opacity: 0.22;
}
.cb-spread-piece:hover {
  scale: 1 1.4;
}
.cb-spread-piece:focus-visible {
  outline: var(--cbc-focus);
  outline-offset: var(--cbc-focus-gap);
}

/* The legend, under the bar it describes (.cb-funnel-split), with the whole
   line to wrap in. */
.cb-spread-keys {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cb-space-sm);
  min-inline-size: 0;
}
.cb-spread-key {
  flex: none;
}
.cb-spread-key {
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
  padding: var(--cb-space-xs) var(--cb-space-lg);
  border: 1px solid transparent;
  border-radius: var(--cb-radius-full);
  background: none;
  color: var(--cbc-ink-muted);
  font-size: var(--cb-type-xs-size);
  cursor: pointer;
  transition:
    color var(--cb-motion-fast) var(--cbc-spring),
    border-color var(--cb-motion-fast) var(--cbc-spring),
    background var(--cb-motion-fast) var(--cbc-spring);
}
.cb-spread-key:hover {
  color: var(--cbc-ink);
  background: var(--cbc-hover);
}
.cb-spread-key[aria-pressed="true"] {
  color: var(--cbc-ink);
  border-color: var(--cbc-line-strong);
  background: var(--cbc-hover);
}
.cb-spread-dot {
  inline-size: var(--cb-space-md);
  block-size: var(--cb-space-md);
  border-radius: var(--cb-radius-full);
  background: var(--tint);
}
.cb-spread-n {
  font-variant-numeric: tabular-nums;
  color: var(--cbc-ink-faint);
}

/* ---- The card the list sits in ----
   No padding of its own, and no gap between its parts. A dashboard card is a
   grid with twenty pixels between every child, which put a hole between the
   filters and the chips, another between the chips and the count, and another
   above the table. These parts belong to each other. They are inset by the
   pieces that already know how to inset themselves; the tables and the trail's
   day headings run the full width so they can sit against a rule. */
.cb-flow > .cb-card:has(.cb-filters) {
  display: block;
  padding: var(--cb-space-3xl) 0;
  /* Cards clip their contents by default, and a clipping ancestor is also a
     scroll container, which quietly makes every sticky descendant inert. The
     day headings below are the reason this one does not clip; nothing in it
     reaches a rounded corner, so there is nothing to clip. */
  overflow: visible;
}
/* THE CONTROLS, AS THEIR OWN SURFACE. Tighter than a card of content, because
   a toolbar is furniture: it is read once on the way to the list and then only
   aimed at. Two rows with a small gap rather than a card's worth of air. */
/* NOTHING ABOVE THE HEADER ROW. The count used to live here and the card's top
   padding was the air around it; the count moved to the toolbar and the padding
   stayed, so the table opened with twenty four pixels of white above a header
   that already carries its own. The card starts at the table now. */
/* NOT `.cb-flow > .cb-card.cb-listing`, which is what this was and which the
   stand-in cannot satisfy: it builds the same card inside its own wrapper
   rather than inside the page flow, so the card kept the full padding every
   other card has and the grey table sat inset by twenty-four pixels from the
   real one. A stand-in that is built out of the real screen has to be able to
   reach the real screen's rules. */
.cb-card.cb-listing {
  display: block;
  padding: 0 0 var(--cb-space-lg);
  overflow: visible;
}
.cb-flow > .cb-card.cb-controls {
  display: grid;
  gap: var(--cb-space-md);
  padding-block: var(--cb-space-xl);
}
.cb-card:has(.cb-filters) > .cb-filters {
  padding-inline: var(--cb-space-3xl);
  /* One row, and the search box absorbs the squeeze.
     The row wrapped, and the way out of the filters is the last thing in it,
     so the first thing to drop onto a second line was the one control that
     has to stay beside the controls it clears. A wrapped item with an auto
     start-margin lands alone and right-aligned, which reads as a button that
     belongs to nothing. Below the phone breakpoint it wraps again, where a
     stacked toolbar is the right answer anyway. */
  flex-wrap: nowrap;
}
.cb-card:has(.cb-filters) > .cb-filters > .cb-input {
  min-inline-size: 0;
}
/* A search box with no menus beside it would otherwise take the whole width of
   the card and read as an empty bar rather than as a field. */
.cb-card:has(.cb-filters) > .cb-filters > .cb-input {
  max-inline-size: var(--cb-width-lg);
}
/* A menu whose options are email addresses sizes itself to the longest one,
   which put a 390px control in this toolbar and pushed the last two filters
   onto a second row. Only that menu is capped, and only because its options
   are addresses: capping the rest cut labels that had room to spare. The
   address is still whole once the menu is open. */
.cb-filters .cb-pick-wrap:has(> .cb-pick[aria-label="Who"]) {
  max-inline-size: var(--cb-space-11xl);
}
.cb-filters .cb-pick-wrap > .cb-pick {
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The way out of a set of filters sits at the far end of the row that set them,
   and is not there at all when there is nothing to clear. This is the one place
   where hiding beats disabling: it is last in its row, so nothing beside it can
   be moved by its going, and a permanent button for a state nobody is in is
   furniture. Everywhere else, rule 15 holds. */
.cb-filters-clear {
  /* At the far end of the row, and stopping where the row stops.
     It was pulled out past the toolbar's inset to line its words up with the
     table's last column, which was right while it was a small borderless
     label. Now that it stands the same height as the fields beside it, it is
     one of them: its box ends where the search box begins on the other side,
     and the hover pill has the card's own padding to sit in instead of
     running into the border. */
  margin-inline-start: auto;
}

/* The two menus whose options come from the data are rebuilt when it arrives.
   The holder they are rebuilt into is a box the layout should not see, so the
   menu itself stays the flex item it was. */
.cb-pick-slot {
  display: contents;
}
.cb-card:has(.cb-filters) > .cb-page-actions {
  padding-inline: var(--cb-space-3xl);
}
.cb-card:has(.cb-filters) > .cb-empty {
  padding-inline: var(--cb-space-3xl);
}

/* ---- THE SENTENCE, WHEREVER IT IS DRAWN ----
   The activity trail is a table now (activity-table.ts), built from the same
   header, row height and column machinery as the other two list screens, and
   the client brief still draws the same sentences as a list of its own
   (client-brief.ts). What both share is the SENTENCE: a person, what they did,
   the thing it happened to, and whatever else the entry knows. So these are the
   sentence's own rules, and each of the two screens brings its own box. */

/* The day grouping that used to head this list (`.cb-trail`, `.cb-trail-day`
   and its two children) is gone with views/activity-trail.ts, which the table
   replaced. It was briefly restored here, because the rules were removed one
   commit before the file that wore them, and for that commit the Activity
   screen drew its date headings with no sticky hold and no ground. Both halves
   have now landed, so the rules go with the elements. The sentence's own rules
   below stay: the client brief still draws them (client-brief.ts). */

.cb-trail-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.cb-trail-row {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr) max-content;
  align-items: start;
  gap: var(--cb-space-lg);
  padding: var(--cb-space-lg) var(--cb-space-3xl);
  transition: background var(--cb-motion-fast) var(--cbc-spring);
}
.cb-trail-row + .cb-trail-row {
  border-block-start: 1px solid var(--cbc-line);
}
.cb-trail-row:hover {
  background: var(--cbc-hover);
}

/* The area, in the icon its own screen wears in the navigation rail. A key
   leaving, or access changing hands, is the entry somebody goes looking for
   months later, so it gets the whole tint and everything else gets a wash of
   it. Marked by weight, never by a bar down the edge (docs/10 on rails). */
.cb-trail-mark {
  display: grid;
  place-items: center;
  flex: none;
  inline-size: var(--cb-space-4xl);
  block-size: var(--cb-space-4xl);
  border-radius: var(--cb-radius-full);
  background: color-mix(in srgb, var(--tint) 16%, transparent);
  color: var(--tint);
}
.cb-trail-row[data-heavy="true"] .cb-trail-mark,
.cb-table tbody tr[data-heavy="true"] .cb-trail-mark {
  background: var(--tint);
  color: var(--cbc-on-tint);
}
.cb-trail-mark .cb-icon {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}

.cb-trail-say {
  min-width: 0;
  font-size: var(--cb-type-sm-size);
  line-height: var(--cb-space-4xl);
  color: var(--cbc-ink-muted);
  overflow-wrap: anywhere;
}
/* In a table it takes `cb-clip` beside this and stops wrapping: one row height
   is what lets the eye run down a column, and the whole sentence is on the
   row's own tooltip. In the brief, where a card has room, it wraps. */
.cb-trail-who {
  font-weight: var(--cb-font-weight-semibold);
  color: var(--cbc-ink);
}
/* The thing it happened to, named the way the rest of the console names it,
   and a link wherever the console has a screen for it. */
.cb-trail-thing {
  font-weight: var(--cb-font-weight-medium);
  color: var(--cbc-ink);
  text-decoration: none;
  border-block-end: 1px solid transparent;
  transition:
    color var(--cb-motion-fast) var(--cbc-spring),
    border-color var(--cb-motion-fast) var(--cbc-spring);
}
a.cb-trail-thing:hover {
  color: var(--cbc-accent);
  border-block-end-color: var(--cbc-accent);
}
.cb-trail-note {
  color: var(--cbc-ink-faint);
}
.cb-trail-note::before {
  content: " \00b7 ";
}

.cb-trail-at {
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-space-4xl);
  color: var(--cbc-ink-faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ---- The day, across the table ----
   The trail is read down a day at a time, so the days are still called by name
   where they still mean something: only in the default order, because a heading
   saying "Yesterday" over rows sorted by who did them would be a heading over
   every day there is (activity-table.ts). */
.cb-table-day > th {
  padding: var(--cb-space-lg) var(--cb-space-xl) var(--cb-space-sm);
  font-size: var(--cb-type-xs-size);
  font-weight: var(--cb-font-weight-semibold);
  text-transform: none;
  letter-spacing: normal;
  color: var(--cbc-ink);
  background: var(--cbc-hover);
  border-bottom: 1px solid var(--cbc-line);
}
/* Nothing to hover: it is a label, not a row anybody can do anything with. */
.cb-table tbody tr.cb-table-day:hover {
  background: none;
}
/* The heaviest changes carry their weight on the whole row as well as on the
   mark, because the mark lives in a column somebody can turn off. */
.cb-table tbody tr[data-heavy="true"] .cb-trail-say {
  color: var(--cbc-ink);
}
/* In a table the sentence is a cell, so it takes the row height from the row
   rather than carrying a line-height of its own. */
.cb-table .cb-trail-say {
  line-height: inherit;
}

/* ---- Arrival ----
   One entrance for the whole screen: the two panels rise in sequence and the
   bar draws itself out from the left once they are there. Only on arrival:
   the screen is repainted on every keystroke in the search box, and a screen
   that re-animates while you type into it is the worst thing motion can do. */
/* A RUN OF CARDS, ARRIVING IN ORDER (views/arrive.ts). The settings screens,
   Team, the credentials list and the Clients board had no entrance at all:
   their cards were simply there on the frame the data landed, which reads as a
   page snapping into place rather than settling into one. The beat is the beat
   every other stagger in this console uses: `--i` on the child, one
   `--cb-motion-step` each.

   A different value from the list screens' "true" below, because they are a
   different thing: three named parts in a bespoke order rather than a run.
   Sharing the name meant the rule below took the second card of every settings
   screen and made the run 0, 150, 180, 270 instead of 0, 90, 180, 270, and one
   beat wrong in a run is exactly what reads as broken. */
[data-arriving="run"] > * {
  animation: cbc-rise var(--cb-motion-base) var(--cbc-spring) backwards;
  animation-delay: calc(var(--i, 0) * var(--cb-motion-step));
}
.cb-flow[data-arriving="true"] > * {
  animation: cbc-rise var(--cb-motion-base) var(--cbc-spring) backwards;
}
.cb-flow[data-arriving="true"] > *:nth-child(2) {
  animation-delay: var(--cb-motion-fast);
}
.cb-flow[data-arriving="true"] .cb-spread-bar {
  animation: cbc-grow-x var(--cb-motion-slow) var(--cbc-spring) backwards;
  animation-delay: var(--cb-motion-base);
}

@media (prefers-reduced-motion: reduce) {
  [data-arriving="run"] > *,
  .cb-flow[data-arriving="true"] > *,
  .cb-flow[data-arriving="true"] .cb-spread-bar {
    animation: none;
  }
  .cb-spread-piece:hover {
    scale: none;
  }
  /* The badge still says "Scanning" in words, so nothing is lost by holding
     the dot still. */
  .cb-table tbody tr[data-status="running"] .cb-cell-status .cb-badge::before,
  .cb-table tbody tr[data-status="queued"] .cb-cell-status .cb-badge::before {
    animation: none;
  }
}

/* 768px mirrors tokens.breakpoint.md */
@media (max-width: 768px) {
  .cb-card:has(.cb-filters) > .cb-filters {
    flex-wrap: wrap;
  }
  .cb-spread-facts {
    gap: var(--cb-space-md);
  }
  /* One column below the breakpoint: two halves of a strip at half a phone
     each is two things nobody can read. */
  .cb-spread {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--cb-space-xl);
  }
  /* And the toolbar stacks in the order it reads: which list, what in it, how
     many that leaves. */
  .cb-listbar {
    grid-template-columns: minmax(0, 1fr);
  }
  .cb-listbar > .cb-count {
    justify-self: start;
  }
  .cb-listbar > .cb-filters {
    flex-wrap: wrap;
  }
  .cb-trail-row {
    grid-template-columns: max-content minmax(0, 1fr);
  }
  .cb-trail-at {
    grid-column: 2;
    line-height: var(--cb-type-xs-line-height);
  }
}

/* ---- What we know ----
   A list of notes, and the one number that makes pinning a decision rather
   than a free-looking switch. */
.cb-notes {
  display: grid;
}
.cb-notes-row {
  display: flex;
  align-items: center;
  gap: var(--cb-space-xs);
  border-bottom: 1px solid var(--cbc-line);
}
.cb-notes-row:last-child {
  border-bottom: 0;
}
.cb-notes-row:hover {
  background: var(--cbc-hover);
}
.cb-notes-open {
  flex: 1 1 auto;
  min-inline-size: 0;
  display: grid;
  gap: var(--cb-space-xxs);
  padding: var(--cb-space-lg) var(--cb-space-xl);
  border: 0;
  background: none;
  color: var(--cbc-ink);
  font: inherit;
  text-align: start;
  cursor: pointer;
}
.cb-notes-title {
  display: flex;
  align-items: center;
  gap: var(--cb-space-md);
}
.cb-notes-summary {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-muted);
}
.cb-notes-when {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-faint);
}
.cb-notes-act {
  display: grid;
  place-items: center;
  inline-size: var(--cb-space-4xl);
  block-size: var(--cb-space-4xl);
  margin-inline-end: var(--cb-space-lg);
  border: 0;
  border-radius: var(--cb-radius-md);
  background: none;
  color: var(--cbc-ink-faint);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--cb-motion-fast) var(--cbc-spring);
}
.cb-notes-row:hover .cb-notes-act,
.cb-notes-act:focus-visible {
  opacity: 1;
}
.cb-notes-act:hover {
  color: var(--cbc-ink);
  background: var(--cbc-surface);
}
.cb-notes-act .cb-icon {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}

.cb-notes-budget {
  display: grid;
  gap: var(--cb-space-md);
  padding: var(--cb-space-xl) var(--cb-space-3xl);
}
.cb-notes-bar {
  position: relative;
  block-size: var(--cb-space-sm);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-line);
  overflow: hidden;
}
.cb-notes-bar::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: var(--share, 0%);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-accent);
  animation: cbc-wipe-x var(--cb-motion-slow) var(--cbc-spring) both;
}
.cb-notes-spent {
  font-size: var(--cb-type-xs-size);
  color: var(--cbc-ink-muted);
}
.cb-notes-spent[data-tone="warn"] {
  color: var(--cbc-warn-text);
}
.cb-note-body {
  font: inherit;
  font-size: var(--cb-type-sm-size);
  resize: vertical;
}

/* ---- Backups ---- */
/* The list is read as a comparison rather than as a set of records: which of
   these is newest, which is biggest, which is the one from before the thing
   that went wrong. So the numbers line up on their right edges and the actions
   sit in one column at the end, which is what makes a column of buttons a
   column rather than three buttons that move with the width of the row beside
   them (rule 15 is the other half of that: a control that does not apply is
   turned off in place, never taken out). */
/* Nowrap as well as tabular: "3.6 MB" broken after the number is a two-line
   cell in a one-line row, and the column stops lining up. */
.cb-num-col {
  text-align: end;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.cb-actions-col {
  text-align: end;
  white-space: nowrap;
  width: 1%;
}
.cb-row-actions {
  display: flex;
  gap: var(--cb-space-xs);
  justify-content: flex-end;
  align-items: center;
}
/* The actions answer the pointer rather than announcing themselves on every
   row. Three buttons on twenty rows is sixty buttons shouting at somebody who
   came to read two numbers, and the row they want is the one they are already
   pointing at. Keyboard focus brings them back, because a control reachable
   only by pointer is not reachable. */
.cb-backup-row .cb-row-actions {
  opacity: 0;
  transition: opacity var(--cb-motion-fast) var(--cbc-spring);
}
.cb-backup-row:hover .cb-row-actions,
.cb-backup-row:focus-within .cb-row-actions {
  opacity: 1;
}
@media (hover: none) {
  /* Nothing hovers on a touch screen, so the affordance has to be there. */
  .cb-backup-row .cb-row-actions {
    opacity: 1;
  }
}
@media (prefers-reduced-motion: reduce) {
  .cb-backup-row .cb-row-actions {
    transition: none;
  }
}

/* Nothing in this column wraps. "3 minutes ago" broken across two lines makes
   the row twice as tall as the ones around it, and a list read as a comparison
   needs its rows the same height. */
.cb-backup-when {
  display: block;
  font-weight: var(--cb-font-weight-medium);
  white-space: nowrap;
}
.cb-backup-why {
  display: block;
  white-space: nowrap;
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-type-xs-line-height);
  color: var(--cbc-ink-faint);
}
/* The copy taken before a restore. It is the undo for something somebody did
   on purpose and retention never prunes it, so the row that matters after a bad
   restore is findable at a glance. The mark is the words themselves gaining
   weight and colour rather than a bar drawn down the edge of the row: a rule
   beside a row is a second visual system for one fact. */
.cb-backup-row[data-undo] .cb-backup-why {
  color: var(--cbc-accent-text);
  font-weight: var(--cb-font-weight-medium);
}
.cb-backup-row td .cb-hint {
  margin-inline-start: var(--cb-space-md);
}
