/* ============================================================================
   Data grid — the spreadsheet LENS for records (Transactions, Invoices).
   An alternative view to the cards for accountants doing bulk/power work.

   Principles (Peter, 24 Jul 2026):
   - Real <table> for the a11y floor (th/td, aria-sort). Wide = scroll inside
     .data-grid__wrap, never the page body.
   - QUIET WHEN FINE, LOUD WHEN NOT. A fully-reconciled row is calm (a faint
     tick, plain text). Gaps — no receipt, VAT?, uncategorised, unlinked — carry
     ALL the visual weight (.data-grid__gap / .data-grid__flag). Never a wall of
     icons: category + VAT shout in their OWN columns; the Status column only
     carries what those columns don't (receipt, unlinked).
   - Editing is DRAWER-ONLY. No inline cell editing, no per-cell dropdowns. The
     gap markers are read-only <span>s; the whole row click opens the edit drawer.
   - Multi-select reuses the app's select convention: selected = grey subtle
     fill + text border (same as the .row-card select mode), NOT the yellow
     highlighter (that's reserved for the just-acted flash).
   ============================================================================ */

/* Smooth Card ⇄ Grid swap — fade the old view down + out, switch, then settle the new one in with a
   little VERTICAL bounce only (no horizontal motion, so the body never shifts sideways). The grid
   stays in the same centered frame as the cards — it never widens, so it can't slide under the sidebar. */
.view-exit { animation: viewExit .17s cubic-bezier(0.32, 0, 0.1, 1) both; }
.view-enter { animation: viewEnter .5s cubic-bezier(0.34, 1.34, 0.64, 1) both; }
@keyframes viewExit { to { opacity: 0; transform: translateY(6px); } }
@keyframes viewEnter { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .view-exit, .view-enter { animation: none; } }

/* THE GRID IS ITS OWN SCROLL AREA (Peter, 16 Sep 2026) — the way a spreadsheet works.
   It used to have no overflow on desktop, on the reasoning that a scroll container would anchor the
   sticky <thead> to the wrap instead of the page, so the column header would scroll away. That was
   the real constraint, and it is why the grid TRUNCATED: table-layout:fixed honours whatever widths
   we write, so the only way to stay inside the card was to shrink every column, and with the drawer
   taking 420px the columns shrank into nonsense (Description 78px at 1440, 60px at 1180, Status
   collapsed to a bare dot). The premise was wrong: measured in the browser, a sticky <th> inside an
   overflow:auto wrap pins to the WRAP's top and stays visible at every scroll position, vertical and
   horizontal. So the wrap is a fixed-height region that scrolls both ways, the <thead> is sticky to
   the top of that region (pinned by construction, it cannot scroll away), the columns keep readable
   widths at every frame width, and you scroll sideways for the rest. The container clips, so the
   table can never paint past the card or past the page header either. Height is written by JS
   (viewport − the wrap's top − the floating bulk bar's clearance). */
.data-grid__wrap {
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  overflow: auto;
  overscroll-behavior: contain;   /* the page behind never takes over at the ends */
}

.data-grid {
  width: 100%;
  /* Fixed layout so the widths we write are honoured exactly. min-width is written by JS as the sum
     of the preferred columns, so the table never shrinks below readable — past that the wrap scrolls
     sideways. Description carries no width, so it takes whatever room is left over. */
  table-layout: fixed;
  border-collapse: collapse;
  font-size: var(--text-sm);
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
}

/* Header — muted, quiet; sticky to the TOP OF THE SCROLL REGION, so it is pinned by construction:
   it cannot slide under the page header (the region starts below it) and it cannot scroll away
   (nothing above it inside the region). It rides sideways with the body columns, which is right —
   the header has to stay over the column it names. */
.data-grid thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--color-surface);
  text-align: left;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--color-text-muted);
  padding: 10px 10px;
  border-bottom: var(--border-width) solid var(--color-border);
  white-space: nowrap;
}
.data-grid thead th.data-grid__th--amount { text-align: right; }
.data-grid thead th.data-grid__th--check { width: 44px; padding-right: 0; }

/* Sortable header = a button filling the cell, with a caret that appears on
   hover / when the column is the active sort. */
.data-grid__sort {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.data-grid__th--amount .data-grid__sort { flex-direction: row-reverse; }
/* Caret only reserves space on the ACTIVE sort column, so short headers (VAT, Date) never clip. */
.data-grid__sort .data-grid__caret { width: 14px; height: 14px; flex: none; display: none; }
.data-grid thead th[aria-sort] .data-grid__caret { display: inline-flex; }
.data-grid thead th[aria-sort="ascending"] .data-grid__caret { transform: rotate(180deg); }
.data-grid thead th[aria-sort] .data-grid__sort { color: var(--color-text); font-weight: var(--weight-medium); }

/* Body cells */
.data-grid tbody td {
  padding: 10px 10px;
  border-bottom: var(--border-width) solid var(--color-border);
  vertical-align: middle;
  white-space: nowrap;
}
/* Truncate ONLY the free-text columns — never the checkbox/status/amount cells (their flex/short
   content was rendering a stray "…" when the whole cell got text-overflow). */
.data-grid__desc, .data-grid__td--muted, .data-grid__td--ell { overflow: hidden; text-overflow: ellipsis; }
.data-grid tbody tr:last-child td { border-bottom: none; }
.data-grid__row { cursor: pointer; }
.data-grid__row:hover td { background: var(--color-bg-subtle-light); }

.data-grid__desc { font-weight: var(--weight-medium); }
.data-grid__td--muted { color: var(--color-text-muted); }
.data-grid__td--amount { text-align: right; font-variant-numeric: tabular-nums; }
.data-grid__amt-in { color: var(--color-success); }

/* Checkbox cell — real <input> (no per-cell affordance smell; it's selection). */
.data-grid__td--check { width: 44px; padding-right: 0; }
.data-grid__cb { display: inline-flex; }
.data-grid__cb input {
  appearance: none; -webkit-appearance: none;
  width: 20px; height: 20px; margin: 0;
  border: 2px solid var(--color-border-strong);
  border-radius: 6px;
  background: var(--color-surface);
  cursor: pointer;
  position: relative;
  flex: none;
}
.data-grid__cb input:checked {
  background: var(--color-text);
  border-color: var(--color-text);
}
.data-grid__cb input:checked::after {
  content: "";
  position: absolute; inset: 0;
  background: no-repeat center/12px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
}
.data-grid__cb input:focus-visible { outline: 2px solid var(--color-hilite-strong); outline-offset: 2px; }

/* Selected row — the app's select-mode look (grey subtle + text border), NOT
   the yellow highlighter. */
.data-grid__row.is-selected td { background: var(--color-bg-subtle); }
.data-grid__row.is-selected td:first-child { box-shadow: inset 3px 0 0 var(--color-text); }

/* Quiet "done" states */
.data-grid__ok {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--color-success);
  opacity: .55;                 /* faint — reassurance, not a shout */
  font-size: var(--text-xs);
}
.data-grid__ok svg { width: 15px; height: 15px; }

/* SUGGESTED value — a category/VAT the system guessed but the user hasn't confirmed.
   Shown muted with a dotted underline (matches the card view, which also shows the
   suggestion) so the row doesn't read as blank — but it's clearly "confirm me", not done. */
.data-grid__sugg {
  color: var(--color-text-muted);
  border-bottom: 1px dotted var(--color-border-strong);
}

/* Row currently open in the edit drawer — a steady yellow box so you can see WHICH
   row the drawer is editing while you walk prev/next (connect the views). Ring, no fill.

   AND IT JUMPS OUT (Peter, 16 Sep 2026): "make the row that's active much darker. It just bleeds
   out, just make that all black so it just totally jumps out." The ring alone was not enough — the
   date and account cells are muted grey and the rest of the list is stepped back, so the open row
   read like any other. Every cell in it goes full-strength ink (the selectors carry the row class so
   they outrank the muted-cell rules without !important), and the quiet "done" marks come back to
   full. DARKNESS ONLY, NO WEIGHT CHANGE — the card list makes its open row full ink and changes
   nothing else, and the two views run one rule (Peter, 16 Sep: "make the logic identical between the
   two"). The gap pills keep their own danger / attention colour — those already shout, and blacking
   them would say the gap is closed. */
.data-grid__row.is-editing td,
.data-grid__row.is-editing td.data-grid__td--muted,
.data-grid__row.is-editing td .data-grid__sugg,
.data-grid__row.is-editing td .data-grid__lock {
  color: var(--color-text);
}
.data-grid__row.is-editing td .data-grid__ok { opacity: 1; }
.data-grid__row.is-editing td {
  box-shadow: inset 0 1px 0 var(--color-hilite-strong), inset 0 -1px 0 var(--color-hilite-strong);
}
.data-grid__row.is-editing td:first-child {
  box-shadow: inset 0 1px 0 var(--color-hilite-strong), inset 0 -1px 0 var(--color-hilite-strong), inset 1px 0 0 var(--color-hilite-strong);
}
.data-grid__row.is-editing td:last-child {
  box-shadow: inset 0 1px 0 var(--color-hilite-strong), inset 0 -1px 0 var(--color-hilite-strong), inset -1px 0 0 var(--color-hilite-strong);
}

/* THE LIST STEPS BACK while one row's drawer is open (Peter, 16 Sep 2026) — the same rule as the card
   list, rolled to the grid on his word. The row being edited keeps its yellow box at full strength;
   every other row drops to --tx-mute; hovering any row brings it straight back so the queue stays
   readable without clicking. The detail drawer lays a transparent scrim over the page, so a CSS
   :hover can never fire here: .is-peek is set through the scrim in JS (txScrimHover). */
.data-grid__row { transition: opacity .3s ease; }
/* ONE NUMBER, THE SAME AS THE CARDS (Peter, 16 Sep: "the way the cards gray out, make that the same
   with the rows on the grid view. Make the logic identical between the two"). The grid briefly ran a
   gentler .75 of its own, because the grid's cells start at grey 117 where the card list's name and
   amount start at near-black 44, so the same opacity bites harder. What changed is that the ACTIVE
   row now goes full black: the contrast is carried by the active row, not by how dark the muted ones
   are. --tx-mute-grid is retired; do not reintroduce a second knob. */
.data-grid:has(.data-grid__row.is-editing) .data-grid__row:not(.is-editing):not(.is-peek) { opacity: var(--tx-mute, .5); }

/* LOUD gap markers — the eye-drawers. Read-only (spans), the row opens the drawer. */
.data-grid__gap {
  display: inline-flex; align-items: center;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--color-danger);
  border: 1px dashed var(--color-danger-border);
  background: var(--color-danger-bg);
  border-radius: var(--radius-full);
  padding: 3px 10px;
}
.data-grid__flag {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--color-active);
}
.data-grid__flag::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--color-active); flex: none;
}
.data-grid__flag--unlinked { color: var(--color-info); }
.data-grid__flag--unlinked::before { background: var(--color-info); }

/* Plain-English explanation on hover/focus — "Unlinked" is jargon on its own. */
.data-grid__flag[data-tip] { position: relative; cursor: help; }
.data-grid__flag[data-tip]::after {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 8px); left: 0;
  white-space: nowrap;
  background: var(--color-text); color: var(--color-surface);
  font-size: var(--text-xs); font-weight: var(--weight-regular);
  padding: 5px 9px; border-radius: var(--radius-sm);
  box-shadow: var(--shadow-dropdown);
  opacity: 0; pointer-events: none; transition: opacity .15s ease; z-index: 9;
}
.data-grid__flag[data-tip]:hover::after, .data-grid__flag[data-tip]:focus-visible::after { opacity: 1; }
@media (hover: none) { .data-grid__flag[data-tip]::after { display: none; } }
.data-grid__statuscell { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px 12px; }

/* Locked / filed-period row — bulk heavier actions skip these. */
.data-grid__row.is-locked { }
.data-grid__lock {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--text-xs); color: var(--color-text-muted);
}
.data-grid__lock svg { width: 13px; height: 13px; }

/* Just-acted flash — the yellow highlighter, then fades (rings flash, never stay). */
.data-grid__row.is-flash td { animation: dgFlash 2.4s ease both; }
@keyframes dgFlash {
  0%, 55% { box-shadow: inset 0 1px 0 var(--color-hilite-strong), inset 0 -1px 0 var(--color-hilite-strong); background: var(--color-hilite); }
  100% { box-shadow: none; background: transparent; }
}
@media (prefers-reduced-motion: reduce) { .data-grid__row.is-flash td { animation: none; } }

/* ---- Bulk-action bar ("Bulk edit") ----------------------------------------
   The point of the grid: select rows, then act on many at once (categorise, set
   VAT, tag, reconcile, export). A titled floating panel in the MOBILE APP's
   FloatingMenu language — white, black icons/text, transparent item buttons,
   16px radius, soft lift. Appears the moment a row is selected. */
.data-gridbar {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 9995;
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-width: calc(100vw - 32px);
  /* Slightly translucent, frosted — the mobile app feel. */
  background: rgba(255, 255, 255, 0.82);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  backdrop-filter: saturate(180%) blur(16px);
  color: var(--color-text);
  border-radius: var(--radius-lg);
  padding: 9px 12px;
  box-shadow: 0 12px 34px rgba(12, 12, 13, 0.16);
}
.data-gridbar__head { display: flex; align-items: baseline; gap: 8px; padding: 0 6px; }
.data-gridbar__title { font-weight: var(--weight-bold); font-size: var(--text-sm); }
.data-gridbar__n { font-size: var(--text-xs); color: var(--color-text-muted); white-space: nowrap; }
.data-gridbar__acts { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }
.data-gridbar__act {
  display: inline-flex; align-items: center; gap: 7px;
  background: none; border: none;
  color: var(--color-text);
  font: inherit; font-size: var(--text-sm); font-weight: var(--weight-medium);
  padding: 7px 10px; border-radius: var(--radius-md);
  cursor: pointer;
  white-space: nowrap;
}
.data-gridbar__act:hover { background: var(--color-bg-subtle); }
.data-gridbar__act:active { transform: scale(0.96); }   /* the FloatingMenu's press feel */
.data-gridbar__act svg { width: 16px; height: 16px; flex: none; color: var(--color-text); }
/* Clear sits top-right in the head, quiet, so the panel stays short. */
.data-gridbar__clear {
  margin-left: auto;
  background: none; border: none; color: var(--color-text-muted);
  font: inherit; font-size: var(--text-xs); cursor: pointer;
  padding: 2px 4px; border-radius: var(--radius-sm);
}
.data-gridbar__clear:hover { color: var(--color-text); text-decoration: underline; }

/* ---- Progress overlay (large sync batches) -------------------------------- */
.data-gridprog {
  position: fixed; left: 50%; bottom: 32px; transform: translateX(-50%);
  z-index: 10050;
  background: var(--color-text); color: var(--color-surface);
  border-radius: var(--radius-md); box-shadow: var(--shadow-dropdown);
  padding: 14px 20px; min-width: 280px;
  font-size: var(--text-sm);
}
.data-gridprog__label { display: flex; justify-content: space-between; gap: 16px; margin-bottom: 8px; }
.data-gridprog__track { height: 6px; border-radius: var(--radius-full); background: rgba(255,255,255,.22); overflow: hidden; }
.data-gridprog__fill { height: 100%; width: 0; background: var(--color-surface); transition: width .12s linear; }

/* ---- View toggle (Cards | Grid) ------------------------------------------- */
.view-toggle {
  display: inline-flex;
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  flex: none;
}
.view-toggle__btn {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--color-surface);
  border: none;
  padding: 8px 14px;
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  color: var(--color-text);
  cursor: pointer;
}
.view-toggle__btn:hover { background: var(--color-bg-subtle); }
.view-toggle__ico { display: inline-flex; }
.view-toggle__btn svg { width: 16px; height: 16px; flex: none; }
.view-toggle__btn:focus-visible { outline: 2px solid var(--color-hilite-strong); outline-offset: -2px; }

@media (max-width: 640px) {
  .data-grid__desc { max-width: 140px; }
  .data-gridbar { gap: 4px; padding: 8px 10px; }
  .view-toggle__btn span { display: none; }   /* icon-only on narrow */
}
