/*
 * Components: search form, results table, chips, pagination, timeline.
 *
 * Every state that carries meaning also carries a text label. Colour alone is not sufficient
 * — partly for WCAG, and partly because these screens are printed into case files in
 * greyscale and read under time pressure.
 */

/* -- Cards and panels -------------------------------------------------------------- */

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--sp-6);
}

.card__title {
  font-size: var(--fs-lg);
  font-weight: 600;
  margin: 0 0 var(--sp-5);
}

/* -- Search form ------------------------------------------------------------------- */

/* The search panel folds. Its heading is the button, so the fold reads as part of the card rather
   than as a control beside it. */
.search-panel__summary {
  align-items: baseline;
  cursor: pointer;
  display: flex;
  gap: var(--sp-4);
  list-style: none;
  user-select: none;
}

.search-panel__summary::-webkit-details-marker {
  display: none;
}

.search-panel__title {
  margin: 0;
}

/* Folded, the panel is the way back to the form and nothing else, so it reads as a control rather
   than as a heading with a caption: navy on the brand's pale tint, with a chevron pointing back the
   way it came. Open, it is a heading with a quiet control beside it. */
.search-panel__toggle {
  align-items: center;
  border-radius: var(--radius-pill);
  color: var(--tx4);
  display: inline-flex;
  font-size: var(--fs-sm);
  gap: var(--sp-2);
}

.search-panel__summary:hover .search-panel__toggle {
  color: var(--brand-secondary);
}

.search-panel:not([open]) .search-panel__toggle {
  background: var(--row-open);
  border: 1px solid var(--row-open-line);
  color: var(--brand-secondary);
  font-size: var(--fs-md);
  font-weight: 600;
  padding: var(--sp-2) var(--sp-4);
}

/* One state's words at a time. Both are in the markup and the browser's own open attribute chooses
   between them, which is what keeps the label true without a script deciding it. */
.search-panel[open] .search-panel__label--folded,
.search-panel:not([open]) .search-panel__label--open {
  display: none;
}

/* Points back to the form when folded, and down at the form it will close when open. */
.search-panel[open] .search-panel__chevron {
  transform: rotate(-90deg);
}

/* Two headings that are taken off the screen in one of their states and kept in the document, exactly
   as `.visually-hidden` keeps things, for anyone navigating by headings: the panel's title while it is
   folded, because it names a form that is not showing; and the period's heading in the refine row,
   because the two labels beneath already say From and To. A utility class cannot carry a state, so the
   two states share this rule. */
.search-panel:not([open]) .search-panel__title,
.search-panel--refined .search__period-legend {
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

.search-panel[open] .search-panel__summary {
  margin-bottom: var(--sp-5);
}

/* One field under another, not a grid. An analyst arrives holding one identifier and reads down the
   list for the field it belongs in; the width is capped so a single-line identifier does not stretch
   into a text area, and so the reading order is the visual order. */
.search__fields {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  max-width: 34rem;
}

/* The period is one thing with two ends, so its two fields share a line under one heading. */
.search__period {
  border: 0;
  display: flex;
  gap: var(--sp-4);
  margin: var(--sp-6) 0 0;
  max-width: 34rem;
  padding: 0;
}

.search__period > .field {
  flex: 1;
  min-width: 0;
}

.search__period-legend {
  color: var(--tx3);
  font-size: var(--fs-md);
  margin-bottom: var(--sp-3);
  padding: 0;
}

.search__actions {
  margin: var(--sp-6) 0 0;
}

/* -- The refine row ------------------------------------------------------------------------ */

/* Once results exist the form is reopened to adjust a search, not to begin one, and a form that reads
   down the whole window covers the answer it is meant to refine. Under the mark the module sets, the
   same fields lay out as one wrapping row above the results: compact inputs, small upright labels, the
   hints withdrawn from view (they still describe the inputs to anyone asking), the period's two dates
   side by side, and the Search button at the row's end. This is the analysts' mockup's refine bar; the
   first screen stays the vertical form that mockup also draws. */
.search-panel--refined .search {
  align-items: flex-end;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-4);
}

/* The fields join the row directly; the wrapper that stacked them contributes no box of its own. */
.search-panel--refined .search__fields {
  display: contents;
}

.search-panel--refined .field {
  flex: 1 1 8.5rem;
  min-width: 8rem;
}

/* Small upright labels, in the darker secondary ink: the lighter one falls under the contrast the
   standard asks of text this size. */
.search-panel--refined .field__label {
  color: var(--tx2);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.search-panel--refined .field__hint {
  display: none;
}

.search-panel--refined .field__input {
  font-size: var(--fs-sm);
  height: var(--control-h-sm);
  padding: 0 var(--sp-3);
}

/* The period keeps its two dates together as one item on the row; its heading is taken off the screen
   by the shared rule above the form. */
.search-panel--refined .search__period {
  display: flex;
  flex: 2 1 17rem;
  gap: var(--sp-3);
  margin: 0;
  max-width: none;
}

/* A correction takes a line of its own beneath the row, after the button rather than between the
   fields and it. Empty, the region has no height and needs no hiding: a live region taken out of the
   layout is a live region whose first announcement can be missed. */
.search-panel--refined .search__errors {
  flex-basis: 100%;
  order: 2;
}

.search-panel--refined .search__actions {
  margin: 0;
}

.search-panel--refined .search__actions .button {
  font-size: var(--fs-sm);
  height: var(--control-h-sm);
  padding: 0 var(--sp-4);
}

/* A field the interface offers and cannot yet act on. Dimmed as a whole rather than only the input, so
   the label and the hint that says why read as one unavailable thing. */
.field--disabled {
  opacity: 0.55;
}

.field--disabled .field__input {
  background: var(--panel);
  cursor: not-allowed;
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* Full body weight and the primary text colour, as in the mockup. These labels name the thing an
   analyst arrived holding — an account number, a session — and they are the first thing read on
   the screen, not a caption under something else. */
.field__label {
  color: var(--tx);
  font-size: var(--fs-md);
  font-weight: 600;
}

.field__hint {
  color: var(--tx3);
  font-size: var(--fs-sm);
}

.field__input {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  height: var(--control-h);
  padding: 0 var(--sp-4);
}

.field__input:focus-visible {
  border-color: var(--brand-secondary);
}

.field__error {
  color: var(--red);
  font-size: var(--fs-xs);
}

.button {
  align-items: center;
  background: var(--brand-primary);
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: #fff;
  cursor: pointer;
  display: inline-flex;
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  font-weight: 600;
  gap: var(--sp-2);
  height: var(--control-h);
  justify-content: center;
  padding: 0 var(--sp-5);
}

/* Outlined in the secondary brand colour rather than in the hairline grey. In the mockup every
   control that is not the primary action reads as navy-on-white, which keeps a single orange
   button per screen doing the work of saying which action is the main one. */
.button--secondary {
  background: var(--card);
  border-color: var(--brand-secondary);
  color: var(--brand-secondary);
  font-size: var(--fs-sm);
  height: var(--control-h-sm);
  padding: 0 var(--sp-4);
}

.button--secondary:hover {
  background: var(--row-open);
}

.button:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

/* -- Chips ------------------------------------------------------------------------- */

.chip {
  align-items: center;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--tx2);
  cursor: pointer;
  display: inline-flex;
  font-size: var(--fs-sm);
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-3);
}

/* Filled, not tinted. A pressed filter is a claim about what the table below is now showing, and a
   tint reads as decoration where a fill reads as state. */
.chip[aria-pressed='true'] {
  background: var(--brand-secondary);
  border-color: var(--brand-secondary);
  color: #fff;
}

.chip[aria-pressed='true'] .chip__count {
  color: rgb(255 255 255 / 80%);
}

/* A signal named on a row is a tag, not a control: square-ish, quiet, and never mistaken for
   something to press. */
/* The count of signals the controls raised, beside the named ones: a figure, set apart from the words. */
.chip--count {
  background: var(--card);
  border-color: var(--line);
  border-radius: var(--radius-xs);
  cursor: default;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  padding: 2px 7px;
}

.chip--signal {
  background: var(--panel);
  border-color: var(--line-soft);
  border-radius: var(--radius-xs);
  cursor: default;
  font-size: var(--fs-sm);
  padding: 2px 7px;
}

/* A chip whose underlying measure is unavailable is disabled rather than shown as zero:
   a count of nothing reads as 'none found', which is a different and dangerous claim. */
.chip:disabled,
.chip[aria-disabled='true'] {
  cursor: not-allowed;
  opacity: 0.45;
}

.chip__count {
  color: var(--tx3);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.risk {
  border-radius: var(--radius-pill);
  display: inline-block;
  font-size: var(--fs-sm);
  font-weight: 600;
  padding: 2px 9px;
  white-space: nowrap;
}

/* Filled rather than tinted, and the only filled badge in the table. Everything else on a results
   row is grey or hairline, so the one row that needs reading first is the one carrying colour. The
   word 'High' is still written in it — the fill is emphasis, not the message. */
.risk--high {
  background: var(--red);
  color: #fff;
  font-weight: 700;
}

.risk--medium {
  background: var(--amber-bg);
  color: var(--amber);
}

/* Quiet. Low risk is the common case and the mockup gives it no chrome at all; a green badge on
   nine rows out of ten competes with the one that matters. The label still says 'Low'. */
.risk--low {
  background: transparent;
  color: var(--tx3);
  font-weight: 400;
  padding-left: 0;
}

.risk--unavailable {
  background: var(--panel);
  color: var(--tx3);
  font-style: italic;
  font-weight: 400;
}

/* -- Results panel ----------------------------------------------------------------- */

/*
 * The results, as one panel rather than three stacked regions.
 *
 * The orange band is the strongest mark on the screen and it is used exactly once, at the top of the
 * answer. That is the whole reason it works: an analyst returning to a tab knows instantly whether
 * they are looking at a form or at a result, without reading a word.
 */

/* Overflow is left visible, so the column chooser's panel can drop over the table instead of being
   clipped at the panel's edge. The head rounds its own top corners for the same reason; the table
   scrolls inside its own wrapper. */
.results-panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  margin-top: var(--sp-5);
}

.results-panel__head {
  align-items: center;
  background: var(--brand-primary);
  border-radius: calc(var(--radius-lg) - 1px) calc(var(--radius-lg) - 1px) 0 0;
  color: #fff;
  display: flex;
  gap: var(--sp-4);
  justify-content: space-between;
  padding: var(--sp-3) var(--sp-6);
}

.results-panel__title {
  font-size: var(--fs-lg);
  font-weight: 600;
  margin: 0;
}

.results-panel__range {
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  margin: 0;
}

.results-panel__body {
  padding: var(--sp-4) var(--sp-6) var(--sp-6);
}

/* The controls that adjust the table, above it and to the right, where the mockup hangs them. The
   fold's note is the one thing in here that is not a control, so it takes the free space at the left
   and the controls stay where they were. */
.results-panel__toolbar {
  align-items: center;
  display: flex;
  gap: var(--sp-2);
  justify-content: flex-end;
  margin-bottom: var(--sp-4);
}

/* What the list is doing, and the way out of it. Shown only while the list is folded around an open
   session; the module that folds the list is what reveals it. */
.results__fold {
  align-items: center;
  color: var(--tx3);
  display: flex;
  font-size: var(--fs-sm);
  gap: var(--sp-3);
  margin: 0;
  margin-right: auto;
}

/* -- Results table ----------------------------------------------------------------- */

/* Fifteen columns do not fit every window, and a page of rows is taller than most. The table scrolls
   inside its own wrapper rather than the panel, in both directions: sideways so nothing hung over the
   panel is clipped, and downwards so the headings and each row's own controls can hold their place
   while the rows move under them.

   The vertical scroll has to happen here rather than on the page. A sticky element sticks within its
   nearest scrolling ancestor, and this wrapper is one the moment it scrolls sideways — so headings
   told to stay at the top of the page would answer to a scrollport that never moves, and would scroll
   off with the rows.

   A ceiling rather than a height: a short page of results takes only the room it needs and this never
   becomes a scroller at all. The floor keeps the region readable in a short window, where subtracting
   the rest of the page would otherwise leave a table two rows tall. */
.results__scroll {
  max-height: max(20rem, calc(100vh - 14rem));
  overflow: auto;
}

/* What was searched for, above the results, because the form that produced them has folded away. */
.results-terms {
  align-items: center;
  color: var(--tx3);
  display: flex;
  flex-wrap: wrap;
  font-size: var(--fs-sm);
  gap: var(--sp-2) var(--sp-3);
  margin: var(--sp-5) 0 0;
}

.results-terms__term {
  align-items: center;
  background: var(--card);
  border: 1px solid var(--row-open-line);
  border-radius: var(--radius-pill);
  color: var(--brand-secondary);
  display: inline-flex;
  gap: var(--sp-2);
  padding: 2px var(--sp-2) 2px var(--sp-3);
}

/* The way to drop one term without reopening the form: a round control at the pill's trailing edge in
   the pill's own colour. Quiet until pointed at, so the pill still reads as a statement first. The
   last identifier's control is kept in place and disabled rather than removed — a search cannot do
   without one, and a control that vanishes reads as a rendering fault. */
.results-terms__remove {
  align-items: center;
  background: none;
  border: 0;
  border-radius: 50%;
  color: inherit;
  cursor: pointer;
  display: inline-flex;
  font-size: var(--fs-md);
  height: 1.5rem;
  justify-content: center;
  line-height: 1;
  opacity: 0.6;
  padding: 0;
  width: 1.5rem;
}

.results-terms__remove:hover:not([aria-disabled='true']) {
  background: var(--row-open);
  opacity: 1;
}

/* Unavailable rather than disabled, so it can still be reached and its reason heard. */
.results-terms__remove[aria-disabled='true'] {
  cursor: not-allowed;
  opacity: 0.3;
}

.results-terms__label {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.results-terms__value {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
}

/* Separate borders rather than collapsed ones. A collapsed border belongs to the table rather than to
   the cell that declares it and is painted with the table, so the rule under a sticky heading scrolls
   away with the rows while the heading stays. With no spacing between the cells the two look
   identical. */
.results {
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--fs-base);
  width: 100%;
}

.results th,
.results td {
  text-align: left;
  vertical-align: top;
}

.results td {
  border-bottom: 1px solid var(--line-soft);
  padding: var(--sp-3) var(--sp-3);
}

/* A rule under the headings rather than a filled band behind them. The band gave the header the
   same visual weight as the data; the rule lets the first row be the first thing read.

   The headings stay at the top of the scrolling table, because a column of masked account numbers or
   bare timestamps says nothing about itself once its name has scrolled away, and the analyst reading
   the fortieth row of a page is exactly the one who needs to know which column they are in. They take
   the panel's own background: the rows pass underneath, and a transparent heading would be read
   through. */
.results th {
  border-bottom: 1px solid var(--line);
  color: var(--tx3);
  font-size: var(--fs-sm);
  letter-spacing: 0.03em;
  padding: var(--sp-2) var(--sp-3);
  text-transform: uppercase;
  white-space: nowrap;
}

/* Only inside the scrolling wrapper. The session's payment orders table is the same table and does not
   scroll, and a heading told to stick with no scrolling ancestor answers to the window instead —
   which would leave that table's headings hanging over the page. */
.results__scroll .results th {
  background: var(--card);
  position: sticky;
  top: 0;
  z-index: 2;
}

.results tbody tr:hover td {
  background: var(--row-hover);
}

/* The row whose session is open beside the results. Tinted the same pale navy the interface already
   uses for a chosen thing, with a bar down its leading edge so the mark survives a greyscale print and
   does not depend on the tint alone. Written to outweigh the hover rule above, which is otherwise more
   specific than a single class and would take the mark away under the pointer. */
.results tbody tr.results__row--open td {
  background: var(--row-open);
}

.results tbody tr.results__row--open td:first-child {
  box-shadow: inset 3px 0 0 0 var(--brand-primary);
}

/* Folded around the open session: every other row is taken out of the list rather than dimmed, and
   the toolbar says so and offers them back. Nothing about the match changed and nothing was
   requested — this is the same page of rows with one of them showing, which is why the note says
   'showing' rather than 'filtered'. */
.results-panel--focused .results__row:not(.results__row--open) {
  display: none;
}

/* The way into a session, kept on screen. With every column chosen the table is wider than the window,
   and the last column is the first thing to leave by the right-hand edge — the one cell the analyst
   came to the row for. So it holds its place while the rest of the row scrolls under it, with a rule
   down its leading edge to say that what is beside it has moved.

   It is the actions column because it is the last column, and the actions are last because they are
   not a column the chooser can take away. Each cell carries the panel's background, or the cells
   sliding under it would show through — which is why the row tints have to be stated again below. */
.results__scroll .results th:last-child,
.results__scroll .results td:last-child {
  background: var(--card);
  box-shadow: -1px 0 0 0 var(--line-soft);
  position: sticky;
  right: 0;
}

/* Above the sticky headings as well as the rows: this cell and the heading over it are both stuck, and
   where they meet in the top right corner the heading is the one that must be on top. */
.results__scroll .results th:last-child {
  z-index: 3;
}

/* Said again for the pinned cell. The row tints above are written for a cell with no background of its
   own, and this one needs one — so without these two the actions cell would be the one cell in a
   hovered or open row still showing the panel's plain background, and the mark that says which session
   is open would stop short of the control that opened it. */
.results__scroll .results tbody tr:hover td:last-child {
  background: var(--row-hover);
}

.results__scroll .results tbody tr.results__row--open td:last-child {
  background: var(--row-open);
}

/* Two ways into one session, on one line, so a narrow window does not stack them into a cell twice
   the height of every other. */
.results__actions {
  align-items: center;
  display: inline-flex;
  gap: var(--sp-2);
  white-space: nowrap;
}

.results__sort {
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
  font: inherit;
  letter-spacing: inherit;
  padding: 0;
  text-transform: inherit;
}

.results th[aria-sort='ascending'] .results__sort,
.results th[aria-sort='descending'] .results__sort {
  color: var(--brand-secondary);
}

.results th[aria-sort='ascending'] .results__sort::after {
  content: ' ▲';
}

.results th[aria-sort='descending'] .results__sort::after {
  content: ' ▼';
}

.results__numeric {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.results__mono {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
}

/* An identifier that is also the way to search on it. It keeps the cell's own type and reads as the
   value first — a dotted rule beneath says it can be pressed, and the brand colour arrives under the
   pointer. Not a filled button: fifteen filled buttons in a column would be a column of buttons rather
   than a column of identifiers. */
.results__pivot {
  background: none;
  border: 0;
  border-bottom: 1px dotted var(--tx4);
  border-radius: 0;
  color: inherit;
  cursor: pointer;
  font: inherit;
  padding: 0;
  text-align: left;
}

.results__pivot:hover {
  border-bottom-color: var(--brand-secondary);
  color: var(--brand-secondary);
}

/* A client string is long and nobody reads it whole; it is scanned for a browser name and a version.
   Smaller, and allowed to wrap, so it does not set the row height for every other column. */
.results__client {
  color: var(--tx2);
  font-size: var(--fs-xs);
  max-width: 16rem;
  overflow-wrap: anywhere;
}

/* A value the search index does not yet carry. Said in words, in the same voice as an unpublished risk
   band: never blank, which reads as 'none', and never a dash, which this table uses for a value that is
   genuinely absent on the row. */
.results__unavailable {
  color: var(--tx3);
  font-size: var(--fs-sm);
  font-style: italic;
}

/* -- Masked values ----------------------------------------------------------------- */

.masked {
  align-items: center;
  display: inline-flex;
  gap: var(--sp-1);
}

.masked__value {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
}

.masked__reveal {
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--tx2);
  cursor: pointer;
  font-size: var(--fs-xs);
  padding: 0 var(--sp-1);
}

.masked--revealed .masked__value {
  background: var(--brand-primary-tint);
}

/* A disclosed value is marked as disclosed, permanently and visibly. The tint is not decoration:
   an analyst reading a screen has to be able to tell which values were shown on request — and
   therefore recorded against them — from the ones that were always on it. */
.masked__note {
  color: var(--tx3);
  font-size: var(--fs-xs);
  white-space: nowrap;
}

/* An absent value and a withheld one are the same width and different colours, so a column of
   them reads as a column rather than as a mixture of failures. */
.masked--absent .masked__value,
.masked--refused .masked__value {
  color: var(--tx3);
}

.masked--refused .masked__note {
  color: var(--red);
}

/* -- Pagination -------------------------------------------------------------------- */

.pagination {
  align-items: center;
  display: flex;
  gap: var(--sp-3);
  justify-content: space-between;
  margin-top: var(--sp-4);
}

.pagination__status {
  color: var(--tx2);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}

/* -- Request indicators ------------------------------------------------------------ */

/* These rules belong to the partial-update library, and they are written here rather than left to it.
   It injects them as a stylesheet at runtime, and this application's policy allows stylesheets only
   from this origin carrying a per-response nonce — so the injected block is refused and the indicator
   would sit visible on the page from first render, announcing a request that is not happening.

   Keeping them here means the one thing the library needs from a stylesheet is served the same way as
   every other style: as a file, from this origin, under the policy. The library is told not to inject
   its own copy, in the document head, so there is no refused block and no console error to wade
   through when looking for a real policy violation. */
.htmx-indicator {
  opacity: 0;
  transition: opacity 200ms ease-in;
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
  opacity: 1;
}

/* -- Export ------------------------------------------------------------------------ */

.results__export {
  align-items: center;
  border-top: 1px solid var(--line-soft);
  display: flex;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
}

/* The warning sits beside the control rather than appearing after it is pressed. An analyst about to
   take customer records out of an audited system should be told so before they click, not after. */
.results__export-note {
  color: var(--tx2);
  font-size: var(--fs-xs);
}

/* -- States ------------------------------------------------------------------------ */

.state {
  color: var(--tx2);
  padding: var(--sp-6);
  text-align: center;
}

.state__title {
  color: var(--tx);
  font-size: var(--fs-lg);
  font-weight: 600;
  margin: 0 0 var(--sp-2);
}

.state__detail {
  font-size: var(--fs-sm);
  margin: 0 auto;
  max-width: 60ch;
}

.state__correlation {
  color: var(--tx3);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  margin-top: var(--sp-3);
}

/* The next step is set apart from the explanation, because it is the line the analyst came for and
   the one they will look for again the second time this happens. */
.state__next {
  color: var(--tx);
  font-size: var(--fs-sm);
  font-weight: 600;
  margin: var(--sp-3) auto 0;
  max-width: 60ch;
}

.state__reference {
  color: var(--tx3);
  font-size: var(--fs-xs);
  margin-top: var(--sp-4);
}

/* A left edge rather than a filled panel. A failure that fills its region with colour reads as
   more alarming than the data it replaced, and these appear beside working panels. */
.state--error,
.state--warning,
.state--denied {
  border-left: 3px solid var(--line);
  text-align: left;
}

.state--error {
  border-left-color: var(--red);
}

.state--warning {
  border-left-color: var(--amber);
}

/* A refusal is the system working correctly, so it is marked without being coloured as a fault. */
.state--denied {
  border-left-color: var(--brand-secondary);
}

/* -- Session: one deep-dive panel --------------------------------------------------- */

/* The panel and the deep link render one body, so they are styled once, here.

   One card rather than four. The analysts' mockup draws a session as a single object: a navy band
   naming it, the connection detail beneath, and the activity under that. Four stacked cards read as
   four answers to four questions; one card with a band reads as 'this session', which is what the
   thing is. The card's own padding is given up so the band can run edge to edge. */
.session--deep-dive {
  overflow: hidden;
  padding: 0;
}

.session__band {
  align-items: center;
  background: var(--brand-secondary);
  color: #fff;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-4);
  padding: 14px 26px;
}

.session__title {
  font-size: var(--fs-md);
  font-weight: 600;
  margin: 0;
}

.session__title-suffix {
  font-weight: 500;
  opacity: 0.85;
}

/* On navy, every band is the same translucent chip and the word does the work. A red fill for High
   on a navy band would be the one place in the interface where the chip shouts louder than the row
   that opened it. The words are unchanged, including 'not yet available'. */
.session__band .risk {
  background: rgb(255 255 255 / 16%);
  color: #fff;
  font-size: var(--fs-xs);
  font-style: normal;
  font-weight: 600;
  padding: 3px 9px;
}

/* The panel's own controls, pushed to the far end of the band. Absent on the page. */
.session__actions {
  align-items: center;
  display: flex;
  gap: 10px;
  margin: 0 0 0 auto;
}

.session__popout {
  align-items: center;
  background: rgb(255 255 255 / 14%);
  border: 1px solid rgb(255 255 255 / 32%);
  border-radius: var(--radius-sm);
  color: #fff;
  display: inline-flex;
  font-size: var(--fs-xs);
  font-weight: 600;
  gap: 7px;
  height: 28px;
  padding: 0 11px;
  text-decoration: none;
}

.session__popout:hover {
  background: rgb(255 255 255 / 24%);
}

.session__close {
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  color: #fff;
  cursor: pointer;
  display: inline-flex;
  padding: var(--sp-1);
}

.session__close:hover {
  background: rgb(255 255 255 / 14%);
}

.session__band :focus-visible {
  outline-color: #fff;
}

.session__content {
  padding: var(--sp-5) 30px 28px;
}

/* Every section after the detail is set off by a rule rather than by a card edge. */
.session__timeline,
.session__payments,
.session__linked {
  border-top: 1px solid var(--line-soft);
  margin-top: 20px;
  padding-top: 18px;
}

.session__section-title {
  font-size: var(--fs-base);
  font-weight: 600;
  margin: 0 0 var(--sp-3);
}

/* -- Sections that fold ---------------------------------------------------------------- */

/* The detail and the orders fold away to give the activity the room, the way the search panel folds:
   the heading is the control, the browser owns the state, and each state has its own word — 'Hide'
   while open, 'Show' while folded — chosen by the attribute the browser itself sets. */
.session__fold-summary {
  align-items: baseline;
  cursor: pointer;
  display: flex;
  gap: var(--sp-3);
  list-style: none;
  user-select: none;
}

.session__fold-summary::-webkit-details-marker {
  display: none;
}

.session__fold-summary .session__section-title {
  margin: 0;
}

.session__fold[open] .session__fold-summary {
  margin-bottom: var(--sp-3);
}

.session__fold-toggle {
  align-items: center;
  color: var(--tx3);
  display: inline-flex;
  font-size: var(--fs-xs);
  font-weight: 600;
  gap: var(--sp-1);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.session__fold-summary:hover .session__fold-toggle,
.session__fold-summary:focus-visible .session__fold-toggle {
  color: var(--brand-secondary);
}

/* Points down at what it will close while open, and along the line at what it will reveal while folded. */
.session__fold[open] .session__fold-chevron {
  transform: rotate(-90deg);
}

.session__fold[open] .session__fold-label--folded,
.session__fold:not([open]) .session__fold-label--open {
  display: none;
}

.session__event-count {
  color: var(--tx4);
  font-size: var(--fs-sm);
  font-weight: 400;
  margin-left: var(--sp-2);
}

/* Label over value, in as many columns as the width allows. A two-column list put the value a long
   way from its label on a wide screen; a grid puts more facts in view instead. */
.session__facts {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  margin: 0;
}

.session__fact dt {
  color: var(--tx4);
  font-size: var(--fs-xs);
  margin: 0 0 3px;
}

/* A value breaks rather than runs into its neighbour: a version-six address is longer than a column. */
.session__fact dd {
  font-size: var(--fs-base);
  margin: 0;
  overflow-wrap: anywhere;
}

.session__fact--wide {
  grid-column: 1 / -1;
}

.session__user-agent {
  font-size: var(--fs-sm);
}

.session__open {
  color: var(--tx2);
  font-style: italic;
}

.session__device-label {
  color: var(--tx2);
  margin-left: var(--sp-2);
}

.session__signals {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0;
}

/* -- Timeline head ------------------------------------------------------------------ */

.timeline__head {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  justify-content: space-between;
  margin-bottom: 14px;
}

.timeline__heading {
  margin: 0;
}

.timeline__controls {
  display: flex;
  gap: var(--sp-2);
  margin: 0;
}

/* A control that sits beside a heading rather than beneath a form: shorter, and lighter in weight. */
.button--compact {
  font-size: var(--fs-sm);
  font-weight: 500;
  height: var(--control-h-xs);
  padding: 0 12px;
}

/* -- Minimap ------------------------------------------------------------------------ */

/* The whole session at a glance: one tick per event, positioned by time. The ticks and the window
   are placed as a proportion of the lane's width, so the lane is their positioning context — and
   the lane is inset from the track's edges, because a tick at the very first or last instant would
   otherwise sit half outside the box and be clipped.

   Pointer actions are claimed from the browser, because the lane is dragged. Left to the default, a
   drag on a touchscreen scrolls the page instead of moving the timeline, and the control silently
   stops working on exactly the devices where scrolling a long session is worst. */
.minimap {
  margin-bottom: 16px;
}

.minimap__track {
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  cursor: pointer;
  height: 2.375rem;
  margin-bottom: 5px;
  overflow: hidden;
  position: relative;
  user-select: none;
}

.minimap__lane {
  bottom: 0;
  left: 10px;
  position: absolute;
  right: 10px;
  top: 0;
  touch-action: none;
}

/* The lane takes focus; the track shows it. The lane fills the track's height, so a ring drawn on the
   lane would be clipped top and bottom by the track's rounded edge. */
.minimap__lane:focus-visible {
  outline: none;
}

.minimap__track:focus-within {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

.minimap__axis {
  background: var(--line);
  height: 1px;
  left: 0;
  position: absolute;
  right: 0;
  top: 19px;
}

.minimap__tick {
  background: var(--tier-key);
  border: 0;
  border-radius: 2px;
  cursor: pointer;
  height: 17px;
  padding: 0;
  position: absolute;
  top: 11px;
  /* Centred on its own instant rather than starting at it, so a cluster reads as symmetrical. */
  transform: translateX(-50%);
  width: 3px;
  z-index: 1;
}

/* The tiers are distinguished by height as well as by colour, because a print of a case file and a
   greyscale screenshot both lose the colour and neither loses the shape. Routine is short and pale;
   key is full height and grey; flagged is full height and coloured, red a little wider than amber. */
.minimap__tick--routine {
  background: var(--tier-routine);
  height: 8px;
  top: 15px;
}

.minimap__tick--key {
  background: var(--tier-key);
}

.minimap__tick--flag {
  background: var(--red);
  width: 4px;
}

.minimap__tick--amber {
  background: var(--amber);
  width: 3px;
}

.minimap__tick--red {
  background: var(--red);
  width: 4px;
}

.minimap__tick:hover {
  outline: 2px solid var(--brand-secondary);
  outline-offset: 1px;
}

/* Where the analyst is now. Above the ticks but translucent, so it locates without obscuring, with a
   grip at either end so it reads as a thing that moves. */
.minimap__window {
  background: rgb(27 63 139 / 10%);
  border: 1px solid rgb(27 63 139 / 42%);
  border-radius: 5px;
  bottom: 3px;
  pointer-events: none;
  position: absolute;
  top: 3px;
  z-index: 2;
}

.minimap__window::before,
.minimap__window::after {
  background: rgb(27 63 139 / 55%);
  border-radius: 1px;
  content: '';
  height: 12px;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 2px;
}

.minimap__window::before {
  left: 2px;
}

.minimap__window::after {
  right: 2px;
}

/* First instant on the left, last on the right, and between them where the analyst is, in words. */
.minimap__labels {
  align-items: baseline;
  color: var(--tx4);
  display: flex;
  font-size: var(--fs-2xs);
  justify-content: space-between;
  margin: 0;
}

.minimap__readout {
  color: var(--brand-secondary);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

/* -- Timeline ----------------------------------------------------------------------- */

/* A bounded viewport, which is what makes the timeline scroll rather than the page. It is also what
   the virtualiser measures: the number of rows kept in the document is derived from this height, so
   an unbounded scroller would put every row of a two-thousand-event session in the document and
   defeat the whole arrangement. A maximum rather than a fixed height, so a short session does not
   sit above a blank.

   The ceiling follows the window. The mockup's 330 pixels showed four events on a screen with room
   for a dozen, and the analysts read the activity, not the space under it; so the scroller takes a
   share of the viewport with the mockup's height as its floor, and a short window gets the mockup's
   box rather than a slot. */
.timeline__scroller {
  max-height: max(20.625rem, 60vh);
  overflow-y: auto;
  padding-right: 8px;
  position: relative;
}

.timeline__scroller::-webkit-scrollbar {
  width: 9px;
}

.timeline__scroller::-webkit-scrollbar-thumb {
  background: var(--tier-routine);
  border-radius: 5px;
}

.timeline__canvas {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Positioning is applied only once the browser has taken the rows over.
   Before that — and if the module fails to load at all — the rows stay in normal flow and the
   timeline is a long but perfectly readable list. That is the intended unenhanced state: the server
   has already sent every row, so losing the script should cost the analyst the navigation aids and
   nothing else. */
.timeline__canvas--virtualized {
  position: relative;
}

.timeline__canvas--virtualized > .timeline__event {
  left: 0;
  position: absolute;
  right: 0;
  top: 0;
}

/* Only the routine tier collapses, and it collapses to nothing rather than to a smaller row: the
   heights the virtualiser holds are recomputed when it does, so the remaining events close up
   instead of leaving the gaps their hidden neighbours occupied. */
.timeline__canvas--hide-routine > .timeline__event--routine {
  display: none;
}

/* Three columns: when, what kind, what happened. */
.timeline__event {
  border-radius: var(--radius);
  display: flex;
  gap: 12px;
  padding: 9px 10px 9px 4px;
}

.timeline__event:hover {
  background: var(--panel);
}

/* Wider than the mockup's gutter by the width of an interval stated in hours: a gap of a day reads
   as one line here rather than as three. */
.timeline__gutter {
  flex: 0 0 68px;
  padding-top: 3px;
  text-align: right;
  width: 68px;
}

.timeline__time {
  color: var(--tx2);
  display: block;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}

/* The interval since the event before. Kept to one line even when empty, so the first row's clock
   sits at the same height as every other row's. */
.timeline__delta {
  color: var(--tx4);
  display: block;
  font-size: var(--fs-2xs);
  min-height: 1.2em;
  white-space: nowrap;
}

/* A continuous line through every row, with the event's symbol sitting on it. The line is drawn by
   the rail rather than by the list so it survives virtualisation: each row draws its own segment. */
.timeline__rail {
  display: flex;
  flex: 0 0 32px;
  justify-content: center;
  position: relative;
  width: 32px;
}

.timeline__rail::before {
  background: var(--line-soft);
  bottom: -9px;
  content: '';
  left: 50%;
  position: absolute;
  top: -9px;
  transform: translateX(-50%);
  width: 1.5px;
}

.timeline__marker {
  align-items: center;
  background: var(--card);
  border-radius: 50%;
  display: flex;
  flex: none;
  height: 28px;
  justify-content: center;
  position: relative;
  width: 28px;
  z-index: 1;
}

.timeline__body {
  flex: 1;
  min-width: 0;
  padding-top: 2px;
}

.timeline__title {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  font-size: var(--fs-base);
  gap: var(--sp-2);
  margin: 0;
}

/* The event type is a control, because the row is expandable and a row is not reachable by
   keyboard. It should not look like a button; it should look like the heading it also is. */
.timeline__disclosure {
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
  font: inherit;
  padding: 0;
  text-align: left;
}

.timeline__disclosure:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

.timeline__type {
  font-weight: 500;
}

/* The word 'flagged' beside a flagged event. Amber is tinted and red is filled, so the two differ in
   weight as well as in hue and a greyscale print still tells them apart. */
.timeline__badge {
  background: var(--amber-bg);
  border-radius: var(--radius-xs);
  color: var(--amber);
  font-size: var(--fs-2xs);
  font-weight: 700;
  line-height: 1.3;
  padding: 2px 6px;
}

.timeline__badge--red {
  background: var(--red);
  color: #fff;
}

.timeline__subtitle {
  color: var(--tx2);
  font-size: var(--fs-sm);
  margin: 3px 0 0;
}

/* The before-and-after reading, as one pill: the old value struck through, the new one bold. */
.timeline__change {
  align-items: center;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  display: inline-flex;
  flex-wrap: wrap;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  gap: 6px;
  margin: 5px 0 0;
  padding: 2px 8px;
}

.timeline__before {
  color: var(--tx4);
  text-decoration: line-through;
}

/* The disclosure control inside a struck-through value is not itself struck through. */
.timeline__before .masked__reveal {
  text-decoration: none;
}

.timeline__after {
  color: var(--tx);
  font-weight: 600;
}

.timeline__arrow {
  color: var(--tx4);
}

.timeline__meta {
  align-items: baseline;
  color: var(--tx4);
  display: flex;
  flex-wrap: wrap;
  font-size: var(--fs-xs);
  gap: var(--sp-3);
  margin: 4px 0 0;
}

.timeline__source {
  color: var(--tx4);
  font-size: var(--fs-xs);
}

.timeline__order {
  color: var(--tx3);
  font-size: var(--fs-xs);
}

/* The source and the agreed ranking disagree. Marked rather than reconciled: whichever is wrong is
   worth somebody knowing about, and a silent choice between them would tell nobody. */
.timeline__disagreement {
  background: var(--amber-bg);
  border-radius: var(--radius);
  color: var(--amber);
  font-size: var(--fs-xs);
  padding: 0 var(--sp-1);
}

/* The three tiers at three prominences. This is the hierarchy that keeps a session readable at two
   thousand events, so it is carried by the symbol's size and fill and by the title's weight, not by
   colour alone: flagged is a filled disc with the symbol in white, key an outlined disc, routine a
   smaller and paler one. */
.timeline__event--flag .timeline__marker {
  background: var(--red);
  color: #fff;
}

.timeline__event--flag .timeline__type {
  font-weight: 600;
}

.timeline__event--amber .timeline__marker {
  background: var(--amber);
}

.timeline__event--key .timeline__marker {
  border: 1.5px solid var(--line);
  color: var(--tx3);
}

.timeline__event--routine .timeline__marker {
  border: 1.5px solid var(--line-soft);
  color: var(--tier-routine-ink);
  height: 20px;
  width: 20px;
}

.timeline__event--routine .timeline__type {
  color: var(--tx3);
  font-weight: 400;
}

.timeline__event--routine .timeline__time {
  color: var(--tx4);
}

/* Where the analyst was last sent — by the minimap, by the keyboard, or by stepping through flagged
   events. Without it a long jump lands somewhere indistinguishable from anywhere else. */
.timeline__event--focused {
  background: var(--row-open);
}

/*
 * The event the analyst last pressed.
 *
 * The disclosure module has always set this class; nothing had ever styled it, so pressing an event
 * moved `aria-expanded` to true and changed nothing on screen — a control that announced it had
 * opened something while appearing to do nothing at all.
 *
 * It marks the row as selected rather than revealing anything, because there is nothing held back to
 * reveal: every part of an event is already rendered. That matches the mockup, where pressing an
 * event highlights it and the reading of it is the row itself.
 */
.timeline__event.is-expanded {
  background: var(--row-open);
  box-shadow: inset 2px 0 0 var(--brand-secondary);
}

/* -- Values shared by the results table and the session ----------------------------- */

.timestamp {
  display: inline-flex;
  flex-direction: column;
}

/* The coordinated reading is what correlates against upstream logs, and it is secondary to the one
   the analyst works in. Both are always present; only one is prominent. */
.timestamp__utc {
  color: var(--tx3);
  font-size: var(--fs-xs);
}

.origin {
  display: inline-flex;
  flex-direction: column;
}

.origin__connection {
  color: var(--tx2);
  font-size: var(--fs-xs);
}

/* A concealed origin is a signal in its own right, so it is marked rather than merely reported. */
.origin--concealed .origin__country::after {
  color: var(--amber);
  content: ' ▲';
}

.results__more {
  color: var(--tx3);
  font-size: var(--fs-xs);
}

.skeleton {
  animation: skeleton-pulse 1.4s ease-in-out infinite;
  background: var(--line-soft);
  border-radius: var(--radius-xs);
  height: 1em;
}

@keyframes skeleton-pulse {
  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.5;
  }
}

/* -- Column chooser ---------------------------------------------------------------- */

/*
 * The controls that decide which columns the results list carries, and in what order.
 *
 * One button, and a panel hung off it. Choosing columns is an adjustment to the table the analyst is
 * looking at, and it reads better with the table still in view than as a region stacked above it, so
 * the panel drops over the table instead of pushing it down. The button is outlined in the secondary
 * colour like every control that is not the primary action, and fills when the panel is open so the
 * two states cannot be confused.
 *
 * Inside the panel the columns are laid out as two lists of fixed-height rows rather than as wrapping
 * pills. Order is what these controls exist to change, and a row of pills that wraps cannot show it:
 * the first column of the second line sits under the last column of the first, which is the opposite
 * of the order it means. Rows put the position in the left margin and every control at the same offset
 * on every line. Disabled controls stay in place rather than disappearing at the ends of the list; a
 * control that vanishes under the pointer is how a press lands on the wrong thing.
 */

.columns {
  display: inline-block;
  position: relative;
}

/* The button. A summary styled as one rather than a button, because the browser then owns the open
   state and announces it, and the panel works with no script at all. */
.columns__summary {
  align-items: center;
  background: var(--card);
  border: 1px solid var(--brand-secondary);
  border-radius: var(--radius);
  color: var(--brand-secondary);
  cursor: pointer;
  display: inline-flex;
  font-size: var(--fs-sm);
  font-weight: 600;
  gap: var(--sp-2);
  height: var(--control-h-sm);
  list-style: none;
  padding: 0 var(--sp-4);
  user-select: none;
}

.columns__summary::-webkit-details-marker {
  display: none;
}

/* A chevron rather than the browser's triangle, so it reads as a menu and not as a folded section. */
.columns__summary::after {
  border-bottom: 1.5px solid currentcolor;
  border-right: 1.5px solid currentcolor;
  content: '';
  height: 5px;
  margin-left: var(--sp-1);
  transform: translateY(-2px) rotate(45deg);
  transition: transform 150ms ease;
  width: 5px;
}

.columns[open] .columns__summary::after {
  transform: translateY(1px) rotate(-135deg);
}

.columns__summary:hover {
  background: var(--row-open);
}

.columns[open] .columns__summary {
  background: var(--brand-secondary);
  color: #fff;
}

.columns__summary:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* What the panel would say if it were open, said while it is shut. */
.columns__summary-meta {
  font-size: var(--fs-xs);
  font-weight: 500;
  opacity: 0.75;
}

.columns__panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 10px 28px rgb(16 24 40 / 14%);
  overflow: hidden;
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  width: 20rem;
  z-index: 40;
}

.columns__head {
  align-items: center;
  background: var(--panel);
  border-bottom: 1px solid var(--line-soft);
  display: flex;
  justify-content: space-between;
  padding: var(--sp-3) var(--sp-4);
}

.columns__title {
  color: var(--tx2);
  font-size: var(--fs-sm);
  font-weight: 600;
}

/* A link-shaped button: it is the one control in the panel that discards the analyst's whole
   arrangement rather than adjusting it, and it should not read as one more row control. */
.columns__reset {
  background: none;
  border: 0;
  color: var(--brand-secondary);
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-xs);
  padding: 0;
  text-decoration: underline;
}

.columns__group {
  border: 0;
  margin: 0;
  min-width: 0;
  padding: var(--sp-3) var(--sp-4);
}

.columns__group + .columns__group {
  border-top: 1px solid var(--line-soft);
}

.columns__legend {
  color: var(--tx3);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 0;
  text-transform: uppercase;
}

/* Says what the numbering means, once, instead of a tooltip on each row. */
.columns__legend-hint {
  color: var(--tx4);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}

.columns__list {
  list-style: none;
  margin: var(--sp-2) 0 0;
  max-height: 18rem;
  overflow-y: auto;
  padding: 0;
}

.columns__item {
  align-items: center;
  display: flex;
  gap: var(--sp-2);
  height: 2rem;
  padding: 0;
}

.columns__list--shown .columns__item:hover {
  background: var(--row-hover);
}

.columns__ordinal {
  color: var(--tx4);
  flex-shrink: 0;
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  text-align: right;
  width: 1rem;
}

.columns__name {
  color: var(--tx2);
  flex: 1;
  font-size: var(--fs-sm);
  font-weight: 500;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The locked column says it stays, where the controls would be, so the row does not look like one
   whose controls failed to render. */
.columns__locked {
  color: var(--tx4);
  font-size: var(--fs-xs);
  letter-spacing: 0.03em;
  padding-right: var(--sp-1);
  text-transform: uppercase;
}

.columns__controls {
  display: flex;
  flex-shrink: 0;
  gap: 2px;
}

.columns__control {
  align-items: center;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--brand-secondary);
  cursor: pointer;
  display: inline-flex;
  font: inherit;
  font-size: var(--fs-sm);
  height: 1.375rem;
  justify-content: center;
  line-height: 1;
  padding: 0;
  width: 1.375rem;
}

.columns__control:hover:not(:disabled) {
  background: var(--row-open);
  border-color: var(--row-open-line);
}

/* Taking a column away is the one row control that discards part of the analyst's arrangement, so it
   is the one that answers in a different colour. */
.columns__control--remove:hover:not(:disabled) {
  background: var(--red-bg);
  border-color: #e7c3c3;
  color: var(--red);
}

/* Kept in place at the ends of the list rather than removed, and legible enough to read as 'this
   column is already first' rather than as a rendering fault. */
.columns__control:disabled {
  cursor: default;
  opacity: 0.3;
}

/*
 * An available column: the whole row is the control, so the target is the width of the list rather
 * than the width of a glyph.
 */
.columns__add {
  align-items: center;
  background: var(--card);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--tx2);
  cursor: pointer;
  display: flex;
  font: inherit;
  gap: var(--sp-2);
  height: 100%;
  padding: 0 var(--sp-2);
  text-align: left;
  width: 100%;
}

.columns__add:hover {
  background: var(--row-open);
  border-color: var(--row-open-line);
  color: var(--brand-secondary);
}

.columns__plus {
  color: var(--brand-secondary);
  flex-shrink: 0;
  font-size: var(--fs-md);
  font-weight: 600;
  line-height: 1;
  margin-left: auto;
  opacity: 0.55;
}

.columns__add:hover .columns__plus {
  opacity: 1;
}

.columns__empty {
  color: var(--tx4);
  font-size: var(--fs-sm);
  margin: var(--sp-2) 0 0;
}

.columns__foot {
  background: var(--panel);
  border-top: 1px solid var(--line-soft);
  color: var(--tx4);
  font-size: var(--fs-xs);
  margin: 0;
  padding: var(--sp-2) var(--sp-4);
}
