/* ==========================================================================
   tables.css — enterprise data grids and their small-screen counterpart.

   Two representations of the same rows:
     .data-grid    dense table, used from 768px upwards
     .record-list  compact record rows, used below 768px

   Both are rendered from the same row objects by js/grid.js; responsive.css
   decides which one is visible.
   ========================================================================== */

/* ==========================================================================
   1. SCROLL CONTAINER
   ========================================================================== */
.grid-wrap {
  width: 100%;
  height: 100%;
  overflow: auto;
}

.grid-wrap--scroll {
  overflow-x: auto;
}

/* ==========================================================================
   2. GRID
   ========================================================================== */
.data-grid {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--tn-fs-grid);
  table-layout: auto;
}

/* Header structure comes from the surface change plus a single firm rule -
   no vertical boxing. */
.data-grid thead th {
  position: sticky;
  top: 0;
  z-index: var(--tn-z-sticky);
  height: var(--tn-h-grid-head);
  padding: 0 var(--tn-sp-5);
  background: var(--tn-grid-head-bg);
  border-bottom: 1px solid var(--tn-border-strong);
  color: var(--tn-grid-head-text);
  font-size: var(--tn-fs-sm);
  /* 600, not 700. Plex carries more weight per step than the previous UI
     face, so 700 here read as heavy against a 400 body row rather than as
     structure. 600 keeps the header/body separation doing its job — the
     colour and the rule under it are the other half of that job. */
  font-weight: var(--tn-fw-semibold);
  letter-spacing: 0.02em;
  text-align: left;
  white-space: nowrap;
  vertical-align: middle;
}

.data-grid thead th.is-sortable {
  cursor: pointer;
  user-select: none;
}
/* The resize strip is positioned against the header cell, which is already
   position:sticky — a positioned value, so it is the containing block. Do
   NOT add position:relative here: it would win over sticky and unstick every
   grid header in the prototype.
   ---------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   COLUMN RESIZE + REORDER  (behaviour: js/grid-layout.js)

   The whole affordance is one 9px strip on the trailing edge of a header.
   It is invisible until the pointer is near it, so a dense header stays a
   header rather than becoming a row of handles.
   -------------------------------------------------------------------------- */
.col-resize {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 1;
  width: 10px;
  height: 100%;
  cursor: col-resize;
  /* the visible hairline is drawn inside the strip, so the grab area stays
     comfortably wider than the line the user aims at */
  background: none;
  touch-action: none;
}

.col-resize::after {
  content: "";
  position: absolute;
  top: 20%;
  right: 1px;
  width: 1px;
  height: 60%;
  background: var(--tn-border-strong);
  opacity: 0;
  transition: opacity var(--tn-dur-fast) var(--tn-ease);
}

.col-resize:hover::after,
.data-grid.is-col-resizing .col-resize::after {
  opacity: 1;
}

/* While either gesture runs, the pointer keeps its meaning everywhere on the
   page and nothing can be selected by accident. */
body.is-col-dragging {
  user-select: none;
}

body.is-col-dragging,
body.is-col-dragging * {
  cursor: col-resize;
}

.data-grid.is-col-moving,
.data-grid.is-col-moving .col-resize {
  cursor: grabbing;
}

body.is-col-dragging .data-grid.is-col-moving,
body.is-col-dragging .data-grid.is-col-moving * {
  cursor: grabbing;
}

/* The header being carried: quieted, never removed, so the user keeps the
   reference point they started from. */
.data-grid thead th.is-col-source {
  color: var(--tn-text-muted);
  opacity: 0.55;
}

/* A single line where the column will land — no ghost column, no animation. */
.col-drop-line {
  position: fixed;
  z-index: var(--tn-z-menu);
  width: 2px;
  background: var(--tn-accent);
  pointer-events: none;
}

.col-drag-ghost {
  position: fixed;
  z-index: var(--tn-z-menu);
  transform: translate(12px, 12px);
  max-width: 240px;
  padding: var(--tn-sp-2) var(--tn-sp-4);
  overflow: hidden;
  background: var(--tn-surface-overlay);
  border: 1px solid var(--tn-accent-border);
  border-radius: var(--tn-radius-sm);
  box-shadow: var(--tn-shadow-sm);
  color: var(--tn-text);
  font-size: var(--tn-fs-sm);
  font-weight: var(--tn-fw-semibold);
  white-space: nowrap;
  text-overflow: ellipsis;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   FIXED LAYOUT — only after the user has resized a column in this grid

   Until then the grid keeps the approved automatic layout and this block is
   inert. Once it applies, the widths written onto the header cells by
   js/grid.js are exact, and .col-spacer absorbs whatever is left over so the
   header still spans the panel.
   -------------------------------------------------------------------------- */
.data-grid[data-layout="fixed"] {
  table-layout: fixed;
}

/* Content must be allowed to clip once a column can be made narrower than
   its text, and the max-widths that shaped the automatic layout would
   otherwise cap what the user is allowed to drag to. */
.data-grid[data-layout="fixed"] thead th,
.data-grid[data-layout="fixed"] tbody td {
  max-width: none;
  overflow: hidden;
  text-overflow: ellipsis;
}

.data-grid[data-layout="fixed"] thead th {
  /* the label must not push the column wider than the user asked for */
  padding-right: var(--tn-sp-4);
}

/* Not a column: no border, no padding, no hover target, no menu. */
.col-spacer {
  width: auto;
  min-width: 0;
  padding: 0 !important;
}

.data-grid[data-layout="fixed"] thead th.is-sortable {
  cursor: pointer;
  user-select: none;
}

.data-grid thead th.is-sortable:hover {
  background: var(--tn-neutral-soft);
  color: var(--tn-text);
}

.data-grid thead th .sort-icon {
  margin-left: var(--tn-sp-2);
  color: var(--tn-text-muted);
  font-size: var(--tn-fs-xxs);
}

.data-grid thead th[aria-sort="ascending"],
.data-grid thead th[aria-sort="descending"] {
  color: var(--tn-accent);
}

.data-grid thead th[aria-sort="ascending"] .sort-icon,
.data-grid thead th[aria-sort="descending"] .sort-icon {
  color: var(--tn-accent);
}

.data-grid tbody td {
  height: var(--tn-h-row);
  padding: 0 var(--tn-sp-5);
  border-bottom: 1px solid var(--tn-grid-border);
  color: var(--tn-text-secondary);
  vertical-align: middle;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   COLUMN SEPARATORS

   The Prices grid already demonstrated that vertical structure helps a dense
   grid scan, but it got there through .group-sep - a class hand-applied in
   page-hotel.js that draws --tn-border-strong at the boundaries BETWEEN
   header groups (Adult / 1. Child / 2. Child / Extra Bed). That is a group
   marker, not a column separator, and only Prices has groups.

   This generalises the idea rather than the value: every column boundary gets
   the same weight the ROW separator already uses (--tn-grid-border), so the
   grid reads as an even mesh instead of gaining a second, louder grammar.
   Prices keeps its stronger group rules on top - those selectors carry more
   specificity - which leaves it a readable two-tier structure: subtle between
   columns, firm between groups.

   border-LEFT from the second cell onward, never border-right:
     - one side only, so border-collapse:separate cannot double them up;
     - nothing is drawn at the table's own left or right edge, so the last
       column relies on the panel edge exactly as asked.

   Hidden columns are safe. The Column Chooser omits a hidden column from the
   markup entirely (js/grid.js renders visibleColumns), so the + combinator
   never sees it. The priority classes do use display:none, but column one is
   always priority 1, so a hidden run can never reach the leading edge.
   -------------------------------------------------------------------------- */
.data-grid thead th + th,
.data-grid tbody td + td {
  border-left: 1px solid var(--tn-grid-border);
}

/* The sticky first column already draws its own boundary as a border-right;
   without this the two would meet as a doubled 2px rule. */
.grid-wrap--sticky .data-grid thead th:first-child + th,
.grid-wrap--sticky .data-grid tbody td:first-child + td {
  border-left: 0;
}

/* The slack absorber in a resized grid is not a column and must not be
   separated from the last real one. */
.data-grid .col-spacer {
  border-left: 0;
}

.data-grid tbody tr {
  background: var(--tn-surface);
}

.data-grid tbody tr:hover {
  background: var(--tn-grid-row-hover);
}

/* Selection is a tinted surface plus an accent rail and stronger text —
   never the old full-blue Windows row. */
.data-grid tbody tr.is-selected {
  background: var(--tn-grid-row-selected);
}

.data-grid tbody tr.is-selected td {
  color: var(--tn-text);
  border-bottom-color: var(--tn-accent-border);
}

/* Calm selection: a tinted surface and a 3px accent rail, never a full
   blue row. */
.data-grid tbody tr.is-selected td:first-child {
  box-shadow: inset 3px 0 0 0 var(--tn-grid-row-selected-bar);
}

.data-grid tbody tr:focus-visible {
  outline: none;
  background: var(--tn-grid-row-focus);
  box-shadow: inset 0 0 0 2px var(--tn-accent);
}

@media (prefers-reduced-motion: reduce) {
  .col-resize::after {
    transition: none;
  }
}

.data-grid[data-density="compact"] tbody td {
  height: var(--tn-h-row-compact);
  padding: 0 var(--tn-sp-4);
}

.data-grid[data-density="relaxed"] tbody td {
  height: var(--tn-h-row-relaxed);
}

.data-grid tbody tr.is-muted td {
  color: var(--tn-text-muted);
}

/* ==========================================================================
   3. CELL HELPERS
   ========================================================================== */
.col-indicator {
  width: 22px;
  padding: 0 !important;
  text-align: center;
}

.row-indicator {
  display: inline-block;
  width: 100%;
  color: transparent;
  font-size: var(--tn-fs-xxs);
}

tr.is-selected .row-indicator {
  color: var(--tn-accent);
}

.cell-strong {
  color: var(--tn-text);
  font-weight: var(--tn-fw-semibold);
}

.cell-num {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* Centred, but still numeric: #, Room, Pax, Type and the other short counts
   all land here, and they are compared down the column exactly like the
   right-aligned money is. .cell-num already had this; this column family was
   the gap. */
.cell-center {
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.cell-mono {
  font-family: var(--tn-font-mono);
  font-size: var(--tn-fs-sm);
}

.cell-muted {
  color: var(--tn-text-muted);
}

.cell-negative {
  color: var(--tn-danger);
  font-variant-numeric: tabular-nums;
  text-align: right;
  font-weight: var(--tn-fw-semibold);
}

.cell-primary {
  display: flex;
  flex-direction: column;
  justify-content: center;
  line-height: 1.2;
}

/* One line with an ellipsis keeps rows at a predictable height. The full
   value is never hidden behind hover alone: it is repeated in the row menu
   header, in the mobile record row and in the detail panel. */
.cell-primary__main {
  color: var(--tn-text);
  font-weight: var(--tn-fw-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cell-primary__sub {
  color: var(--tn-text-muted);
  font-size: var(--tn-fs-xs);
}

/* Boolean column rendered as a mark rather than a Windows checkbox */
.cell-flag {
  color: var(--tn-success);
  font-size: var(--tn-fs-base);
}

.cell-flag--off {
  color: var(--tn-border-strong);
}

/* ==========================================================================
   4. COLUMN SIZING
   ========================================================================== */
/* Transfer checkbox column */
.col-select {
  width: 40px;
  padding: 0 !important;
  text-align: center;
}

.data-grid thead th.col-select {
  padding: 0 !important;
}

.col-actions {
  width: 44px;
  text-align: center;
  padding: 0 !important;
}

.grid-action {
  width: 28px;
  height: 28px;
  font-size: var(--tn-fs-base);
}

.col-hotel {
  min-width: 190px;
  max-width: 320px;
}

.col-contract-no { width: 92px; }
.col-market      { width: 150px; }
.col-compact     { width: 78px; }
.col-date        { width: 106px; }
.col-time        { width: 88px; }
.col-currency    { width: 68px; }
.col-status      { width: 108px; }
.col-user        { width: 118px; }
.col-datetime    { width: 132px; }
.col-num         { width: 92px; }
.col-num-sm      { width: 68px; }
.col-nights      { width: 88px; }
.col-code        { width: 96px; }
.col-type        { width: 118px; }
.col-airport     { width: 168px; }

.col-text {
  min-width: 180px;
  max-width: 340px;
}

/* The two wide text columns truncate rather than wrap. Bounded width was
   always the intent of the classes above; wrapping was the side effect,
   and it is the one thing in a grid that can quietly change the row
   height — which is the approved 34px density the whole engine holds.
   Only the body cell truncates: the header carries a sort control, a
   column-search button and a resize strip, none of which may be clipped. */
.data-grid tbody td.col-hotel,
.data-grid tbody td.col-text {
  overflow: hidden;
  text-overflow: ellipsis;
}

.data-grid td.col-date,
.data-grid td.col-datetime,
.data-grid td.col-time {
  font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   5. STICKY EDGES
   ========================================================================== */
/* The sticky divider marks a boundary, it does not need to be a rule. */
.grid-wrap--sticky .data-grid tbody td:first-child,
.grid-wrap--sticky .data-grid thead th:first-child {
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--tn-surface);
  border-right: 1px solid var(--tn-border);
}

.grid-wrap--sticky .data-grid thead th:first-child {
  z-index: 3;
  background: var(--tn-grid-head-bg);
}

.grid-wrap--sticky .data-grid tbody tr:hover td:first-child {
  background: var(--tn-grid-row-hover);
}

.grid-wrap--sticky .data-grid tbody tr.is-selected td:first-child {
  background: var(--tn-grid-row-selected);
}

/* In a horizontally scrolling grid the row menu must stay reachable, so the
   action column is pinned to the right edge instead of scrolling away. */
.grid-wrap--scroll .data-grid th.col-actions,
.grid-wrap--scroll .data-grid td.col-actions {
  position: sticky;
  right: 0;
  z-index: 1;
  background: var(--tn-surface);
  border-left: 1px solid var(--tn-border);
}

.grid-wrap--scroll .data-grid th.col-actions {
  z-index: 3;
  background: var(--tn-grid-head-bg);
}

.grid-wrap--scroll .data-grid tbody tr:hover td.col-actions {
  background: var(--tn-grid-row-hover);
}

.grid-wrap--scroll .data-grid tbody tr.is-selected td.col-actions {
  background: var(--tn-grid-row-selected);
}

/* ==========================================================================
   6. GROUPED HEADERS
   Mirrors the EXE's "1. Child / Age >= / Age <=" and "Cost Price / Allotment"
   header blocks without the Win32 chrome.
   ========================================================================== */
/* [colspan] and not the row: a cell in this row that spans a group IS
   the group label, and one that does not — Room and Accom., which carry
   rowspan=2 — is an ordinary column header and keeps the header
   typography every other grid uses. Keyed to the row, those two read as
   eyebrows beside the sentence-case headers directly below them. */
.data-grid thead tr.group-row th[colspan] {
  height: 26px;
  background: var(--tn-surface-sunken);
  border-bottom: 1px solid var(--tn-border);
  color: var(--tn-text-muted);
  font-size: var(--tn-fs-xs);
  letter-spacing: var(--tn-tracking-caps);
  text-align: center;
  text-transform: uppercase;
}

.data-grid thead tr.group-row th.group-sep,
.data-grid thead tr:not(.group-row) th.group-sep,
.data-grid tbody td.group-sep {
  border-left: 1px solid var(--tn-border-strong);
}

/* ==========================================================================
   7. FOOTER AGGREGATES
   The EXE shows sums under dense grids (181 / 206). Same idea, styled.
   ========================================================================== */
.grid-total {
  display: inline-flex;
  align-items: baseline;
  gap: var(--tn-sp-2);
}

.grid-total__value {
  color: var(--tn-text);
  font-variant-numeric: tabular-nums;
  font-weight: var(--tn-fw-semibold);
}

/* --------------------------------------------------------------------------
   7b. THE SUMMARY ROW  (built by js/grid.js, one <tfoot> per grid)

   It is a row of the SAME table as the data, and that is the whole design.
   A footer built as a separate element beside the grid has to re-measure
   every column width, re-apply every hidden column, mirror the reorder and
   follow the horizontal scroll by hand — and it silently drifts the first
   time one of those is missed. As a <tfoot> it gets all five for nothing.

   Sticky to the BOTTOM of the scroller, the way the header is sticky to the
   top: the count and the totals stay on screen while the rows move under
   them, and they scroll sideways with their own columns because they are
   those columns.
   -------------------------------------------------------------------------- */
.data-grid tfoot.grid-summary td {
  position: sticky;
  bottom: 0;
  z-index: var(--tn-z-sticky);
  height: var(--tn-h-grid-head);
  padding: 0 var(--tn-sp-5);
  background: var(--tn-surface-raised);
  /* The rule goes ABOVE the row — this is a footer, and the line is what
     separates it from the last record rather than underlining the table. */
  border-top: 1px solid var(--tn-border-strong);
  color: var(--tn-text-secondary);
  font-size: var(--tn-fs-sm);
  vertical-align: middle;
  white-space: nowrap;
}

/* tbody's own separator rule is scoped to tbody, so the footer states its
   column boundaries itself rather than inheriting a rule meant for records. */
.data-grid tfoot.grid-summary td + td {
  border-left: 1px solid var(--tn-grid-border);
}

.data-grid tfoot.grid-summary .col-spacer {
  border-left: 0;
}

/* The count is the sentence; the totals are the figures.

   TAKEN OUT OF FLOW, and that is the one thing here that is not obvious.
   In an automatic layout a table column is as wide as its widest cell, so
   "128 records" sitting in the first column would drag a 53px "#" column
   out to 88px — the summary row would silently re-lay out the grid above
   it. Positioned, the label contributes no intrinsic width at all: every
   column keeps exactly the width its DATA gave it, and the sentence simply
   runs on across the blank summary cells to its right, which is where the
   space already was. The footer cell is position:sticky — a positioned
   value — so it is already this label's containing block.

   The totals are deliberately NOT treated this way: a sum belongs under the
   figures it totals, so it has to fit in that column and may widen it. */
.grid-summary__label {
  position: absolute;
  top: 50%;
  left: var(--tn-sp-5);
  transform: translateY(-50%);
  color: var(--tn-text);
  font-family: var(--tn-font-sans);
  /* a mono or centred data column must not restyle the sentence */
  font-weight: var(--tn-fw-semibold);
  font-variant-numeric: tabular-nums;
  letter-spacing: normal;
  white-space: nowrap;
}

.grid-summary__value {
  color: var(--tn-text);
  font-weight: var(--tn-fw-semibold);
  font-variant-numeric: tabular-nums;
}

/* A total belongs under the figures it totals, so it inherits the column's
   own alignment from .cell-num / .cell-center and needs nothing here. */

/* The sticky first column and the pinned action column already paint their
   own background so they can pass over the rows; in the footer they have to
   paint the FOOTER's background or the summary would show the surface colour
   in its two pinned cells. */
.grid-wrap--sticky .data-grid tfoot.grid-summary td:first-child,
.grid-wrap--scroll .data-grid tfoot.grid-summary td.col-actions {
  background: var(--tn-surface-raised);
}

.grid-wrap--sticky .data-grid tfoot.grid-summary td:first-child {
  z-index: calc(var(--tn-z-sticky) + 1);
}

.grid-wrap--scroll .data-grid tfoot.grid-summary td.col-actions {
  z-index: calc(var(--tn-z-sticky) + 1);
}

/* Once a grid is in the fixed layout its columns are exact, and the footer
   has to clip like every other cell rather than pushing a column wider. */
.data-grid[data-layout="fixed"] tfoot.grid-summary td {
  max-width: none;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Except the count, which is out of flow precisely so that it can run on
   over the blank cells beside it — clipping it to its own column would
   throw away the room it was designed to use, and it cannot push anything
   because it has no intrinsic width to push with. */
.data-grid[data-layout="fixed"] tfoot.grid-summary td.grid-summary__count {
  overflow: visible;
}

/* ==========================================================================
   8. RECORD LIST — the mobile representation of a grid row
   ========================================================================== */
.record-list {
  display: none;
}

.record {
  display: flex;
  align-items: stretch;
  gap: var(--tn-sp-4);
  width: 100%;
  padding: var(--tn-sp-4) var(--tn-sp-3) var(--tn-sp-4) var(--tn-sp-5);
  background: var(--tn-surface);
  border: 0;
  border-bottom: 1px solid var(--tn-grid-border);
  text-align: left;
  cursor: pointer;
}

.record:focus-visible {
  outline: none;
  background: var(--tn-grid-row-focus);
  box-shadow: inset 0 0 0 2px var(--tn-accent);
}

.record.is-selected {
  background: var(--tn-grid-row-selected);
  box-shadow: inset 3px 0 0 0 var(--tn-grid-row-selected-bar);
}

.record__check {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  padding-right: var(--tn-sp-2);
}

.record__main {
  flex: 1 1 auto;
  min-width: 0;
}

.record__title {
  display: flex;
  align-items: baseline;
  gap: var(--tn-sp-3);
  color: var(--tn-text);
  font-size: var(--tn-fs-base);
  font-weight: var(--tn-fw-semibold);
  line-height: 1.3;
}

.record__title-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.record__code {
  flex: 0 0 auto;
  color: var(--tn-text-muted);
  font-size: var(--tn-fs-sm);
  font-weight: var(--tn-fw-regular);
}

.record__meta {
  margin-top: 1px;
  color: var(--tn-text-secondary);
  font-size: var(--tn-fs-md);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.record__dates {
  margin-top: 1px;
  color: var(--tn-text-muted);
  font-size: var(--tn-fs-sm);
  font-variant-numeric: tabular-nums;
}

.record__side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--tn-sp-2);
  flex: 0 0 auto;
}

/* --------------------------------------------------------------------------
   PER-COLUMN SEARCH  (behaviour: js/grid-filter.js)

   Two states of one header cell. At rest it is a title with a quiet search
   icon; while it is being searched the title IS the input. Nothing floats,
   nothing is added below the header, and the row stays exactly 36px either
   way — the box is sized to sit inside the header, not to look like a form
   field that was dropped into one.
   -------------------------------------------------------------------------- */

/* ---- the icon ----

   Taken out of the line box and positioned against the header cell, exactly
   as .col-resize already is. An inline 18px control inside a <th> that is
   overflow:hidden under a fixed layout grows the line box and pushes the
   header from 36px to 40px; out of flow it cannot. Its room is reserved with
   padding instead, which costs slightly LESS width than the inline icon did.

   The <th> is position:sticky, which is a positioned value, so it is already
   the containing block. Do NOT add position:relative to it — tables.css says
   why, a few hundred lines up. */
.data-grid thead th.is-filterable {
  padding-right: 32px;
}

/* While the box is open there is no icon, so the reserved room goes back to
   the box — which is also what keeps it clear of the resize strip. */
.data-grid thead th.is-filtering {
  padding-right: var(--tn-sp-5);
}

.data-grid thead th .col-filter {
  position: absolute;
  top: 50%;
  /* 12px clears the 10px grab strip on the trailing edge by 2px */
  right: var(--tn-sp-5);
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  border-radius: var(--tn-radius-sm);
  color: var(--tn-text-muted);
  font-size: var(--tn-fs-xxs);
  opacity: 0;
  /* invisible must also mean unclickable: a header is a sort target, and a
     stray click on something nobody can see would sort instead of search */
  pointer-events: none;
  transition: opacity var(--tn-dur-fast) var(--tn-ease),
              background var(--tn-dur-fast) var(--tn-ease),
              color var(--tn-dur-fast) var(--tn-ease);
}

.data-grid thead th:hover .col-filter,
.data-grid thead th:focus-within .col-filter {
  opacity: 1;
  pointer-events: auto;
}

.data-grid thead th .col-filter:hover {
  background: var(--tn-accent-soft);
  color: var(--tn-accent);
}

.data-grid thead th .col-filter:focus-visible {
  opacity: 1;
  pointer-events: auto;
  outline: 2px solid var(--tn-focus-outline-color);
  outline-offset: 1px;
}

/* Touch has no hover, so on a coarse pointer the action is simply there.
   Below 768px the table is replaced by the record list and there is no
   header to put it on at all — see css/responsive.css. */
@media (hover: none) {
  .data-grid thead th .col-filter {
    opacity: 1;
    pointer-events: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .data-grid thead th .col-filter {
    transition: none;
  }
}

/* ---- the box that replaces the title ---- */

.col-filter-field {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  /* without this the flex item refuses to go below its content width and a
     narrow column would be pushed wider by its own search box */
  min-width: 0;
}

.col-filter-input {
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
  height: 24px;
  /* room on the right for the clear control, which sits over the box */
  padding: 0 20px 0 var(--tn-sp-3);
  background: var(--tn-input-bg);
  border: 1px solid var(--tn-input-border);
  border-radius: var(--tn-radius-sm);
  color: var(--tn-input-text);
  font-family: inherit;
  font-size: var(--tn-fs-sm);
  /* headers are bold; what the operator typed is not a heading */
  font-weight: var(--tn-fw-regular);
  letter-spacing: normal;
}

.col-filter-input::placeholder {
  color: var(--tn-input-placeholder);
  font-weight: var(--tn-fw-regular);
}

.col-filter-input:focus {
  outline: none;
  border-color: var(--tn-accent);
  box-shadow: var(--tn-focus-ring);
}

/* A live filter that is no longer being typed into still has to read as
   "this column is holding rows back", so the box keeps an accent edge. */
.data-grid thead th.is-filtered .col-filter-input {
  border-color: var(--tn-accent);
}

.col-filter-clear {
  position: absolute;
  right: 2px;
  display: inline-grid;
  place-items: center;
  width: 18px;
  height: 18px;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: var(--tn-radius-sm);
  color: var(--tn-text-muted);
  font-size: var(--tn-fs-xs);
  line-height: 1;
  cursor: pointer;
}

.col-filter-clear:hover {
  background: var(--tn-accent-soft);
  color: var(--tn-accent);
}

.col-filter-clear:focus-visible {
  outline: 2px solid var(--tn-focus-outline-color);
  outline-offset: 1px;
}

/* Hidden by the generic DOM applier, which filters a hand-built table by
   hiding rows rather than by re-rendering it. */
.data-grid tbody tr.is-filtered-out {
  display: none;
}

/* ==========================================================================
   9. HOT FIX  (behaviour: js/grid-hotfix.js)

   The exceptional mode: this grid is being typed into directly. Everything
   here is inert until js/grid.js puts .is-hotfix on the table, which it only
   does for the one grid whose control was pressed.

   The mode is stated three ways, and not one of them is colour on its own:
     - the control is aria-pressed and swaps to a filled icon
     - a "Hot Fix on" flag appears beside the panel title
     - the panel and the grid change tone and gain a ring

   Amber, not red. A correction at the counter is an ordinary operation; a
   red grid would say something has gone wrong.
   ========================================================================== */

/* The panel is the object that changes state, because the panel is what the
   operator is looking at. box-shadow, never border-width or padding: the
   ring cannot move a single pixel of the layout underneath it. */
.panel.is-hotfix {
  border-color: var(--tn-hotfix-border);
  box-shadow: 0 0 0 1px var(--tn-hotfix-border), 0 0 0 4px var(--tn-hotfix-ring);
}

.panel.is-hotfix .panel__head {
  background: var(--tn-hotfix-soft);
  border-bottom-color: var(--tn-hotfix-border);
}

.data-grid.is-hotfix thead th {
  border-bottom-color: var(--tn-hotfix-border);
}

/* ---- an editable cell ----
   Quiet at rest — a grid in Hot Fix must still be readable as a grid — and
   explicit under the pointer. The affordance is drawn INSIDE the cell with
   an inset shadow, so nothing reflows on hover. */
.data-grid.is-hotfix tbody td.is-editable {
  cursor: cell;
}

.data-grid.is-hotfix tbody td.is-editable:hover {
  background: var(--tn-hotfix-cell-bg);
  box-shadow: inset 0 0 0 1px var(--tn-hotfix-border);
}

/* The cell is a tab stop while the mode is on, so the keyboard route in has
   to be visible without a pointer ever being used. */
.data-grid.is-hotfix tbody td.is-editable:focus-visible {
  outline: 2px solid var(--tn-hotfix);
  outline-offset: -2px;
  background: var(--tn-hotfix-cell-bg);
}

/* ---- the open editor ----
   The input IS the cell: it carries almost no padding of its own, so the row
   height never changes when an editor opens and the column never widens. */
.data-grid.is-hotfix tbody td.is-editing {
  padding: 0 var(--tn-sp-2);
  background: var(--tn-hotfix-cell-bg);
  box-shadow: inset 0 0 0 1px var(--tn-hotfix);
}

.hotfix-cell {
  display: block;
  width: 100%;
}

.hotfix-input {
  width: 100%;
  min-width: 0;
  height: 26px;
  padding: 0 var(--tn-sp-3);
  background: var(--tn-input-bg);
  border: 1px solid var(--tn-input-border);
  border-radius: var(--tn-radius-sm);
  color: var(--tn-input-text);
  font-family: inherit;
  font-size: var(--tn-fs-grid);
  font-weight: var(--tn-fw-regular);
  letter-spacing: normal;
}

.hotfix-input:focus {
  outline: none;
  border-color: var(--tn-hotfix);
  box-shadow: 0 0 0 2px var(--tn-hotfix-ring);
}

/* Money and counts stay aligned while they are being typed, so the digits do
   not jump across the column the moment an editor opens. */
.cell-num .hotfix-input--num,
.col-num .hotfix-input--num {
  text-align: right;
}

.cell-center .hotfix-input {
  text-align: center;
}

.hotfix-input--select {
  padding-right: 0;
}

/* Refused, and still holding what was typed: a validation failure never
   throws away the operator's work. */
.data-grid.is-hotfix tbody td.is-invalid,
.hotfix-new-row td.is-invalid {
  box-shadow: inset 0 0 0 1px var(--tn-danger);
}

.data-grid.is-hotfix tbody td.is-invalid .hotfix-input,
.hotfix-new-row td.is-invalid .hotfix-input {
  background: var(--tn-danger-soft);
  border-color: var(--tn-danger);
}

/* ---- the new-record row ----
   The last row of the same tbody, so it is a row of THIS table rather than a
   form beside it: same columns, same widths, same horizontal scroll. */
.data-grid.is-hotfix tbody tr.hotfix-new-row > td {
  height: var(--tn-h-row-relaxed);
  padding: 0 var(--tn-sp-2);
  background: var(--tn-hotfix-new-bg);
  /* dashed, because this row is not a record yet */
  border-top: 1px dashed var(--tn-hotfix-border);
  border-bottom: 0;
}

.data-grid.is-hotfix tbody tr.hotfix-new-row:hover > td {
  background: var(--tn-hotfix-new-bg);
}

/* A column that cannot be typed into says so, quietly, rather than leaving a
   gap that would read as a missing editor. */
.hotfix-new__blank {
  display: block;
  color: var(--tn-text-muted);
  text-align: center;
}

.hotfix-new__action {
  text-align: center;
}

.hotfix-new__go {
  width: 26px;
  height: 26px;
  background: var(--tn-hotfix);
  border-color: var(--tn-hotfix);
  color: var(--tn-text-inverse);
  font-size: var(--tn-fs-md);
}

.hotfix-new__go:hover {
  background: var(--tn-hotfix-strong);
  border-color: var(--tn-hotfix-strong);
  color: var(--tn-text-inverse);
}

.hotfix-new__go:focus-visible {
  outline: none;
  border-color: var(--tn-hotfix-strong);
  box-shadow: 0 0 0 3px var(--tn-hotfix-ring);
}

/* The pinned columns paint their own background so they can pass over the
   rows; in the new-record row they have to paint ITS ground instead. */
.grid-wrap--scroll .data-grid.is-hotfix tbody tr.hotfix-new-row > td.col-actions,
.grid-wrap--sticky .data-grid.is-hotfix tbody tr.hotfix-new-row > td:first-child {
  background: var(--tn-hotfix-new-bg);
}
