/* The Review screen (#view-transactions): the review list, its filters, keyboard states
   and the transaction detail. */

/* The transaction tables carry eight columns. In a window too narrow for them they
   scroll sideways inside their card instead of being cut off at its edge, where the
   review buttons were hidden with no way to reach them. */
#dash-unreviewed-container {
  overflow-x: auto;
}

/* ============================================================================
   Review list

   An inbox: rows grouped under day headings, each row a sentence (merchant, then
   account and category) with the amount on the right. Rows carry no boxes; what
   you can do with one shows on hover and focus with a mouse, and by swiping on
   a touch screen.
   ============================================================================ */
.review {
  max-width: 58rem;
  margin: 0 auto;
}

.review-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.review-title {
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--ink);
  margin: 0;
}

.review-progress {
  flex: 0 1 15rem;
  min-width: 9rem;
  text-align: right;
}

/* Hidden, the progress keeps its room: a search hides it once the list's counts land, a
   round trip after the press, and the search and the chips below would otherwise come up
   under a finger already on its way to the next chip. */
.review-progress[hidden] {
  display: block !important;
  visibility: hidden;
}

/* A line each, as tall with a loading bar in it as with a figure and never wrapping, so
   what the progress says doesn't change its room either. */
.review-progress-count,
.review-progress-detail {
  height: 1lh;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.review-progress-count {
  font-size: var(--fs-body-sm);
  font-weight: 600;
  color: var(--ink);
}

/* The count, while anything needs the person, is a link to it, in the amber the rows that
   need them are marked with. Its line clips nothing, so the button's padding reaches past
   the text to a finger's size, its margin taking that back from the layout. */
.review-progress-count:has(.review-progress-link:not([hidden])) {
  overflow: visible;
}

.review-progress-link {
  padding: 12px 8px;
  margin: -12px -8px;
  border: 0;
  background: none;
  color: var(--attn);
  font: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  cursor: pointer;
}

.review-progress-link[hidden] {
  display: none;
}

.review-progress-link:hover {
  text-decoration-thickness: 2px;
}

.review-progress-detail {
  font-size: var(--fs-label);
  color: var(--ink-3);
  margin-top: 2px;
}

/* How much of Needs you is answered: a hairline, not a chart. --done is set from the
   counts through the CSSOM. */
.review-meter {
  height: 4px;
  margin-top: var(--space-2);
  border-radius: var(--radius-full);
  background: var(--sunk);
  overflow: hidden;
}

.review-meter-fill {
  height: 100%;
  width: calc(var(--done, 0) * 100%);
  border-radius: inherit;
  background: var(--accent);
  transition: width var(--dur) var(--ease-out);
}

/* While a search or a filter narrows the list (renderReviewStickiness), the search and
   filters stay under the navbar as it scrolls, to see and undo from anywhere down it;
   otherwise they scroll away with the page. Frosted on the page's colour as the contents
   rows are (.page-toc). Its padding is taken back by its margins, so it stands where it
   would without them until it sticks. Its height while it sticks, --review-top-h, keeps
   the pane beside the list and what the browser scrolls to in the list clear of it.

   --review-stick is where what sticks under the navbar stops: the navbar's foot, or with
   an iPhone's keyboard up, when Safari has scrolled the screen down the page and the
   navbar with it, the screen's top (--vv-top, set in js/events.js). */
#view-transactions {
  --review-stick: max(calc(3.75rem + 1px), var(--vv-top, 0px));
}

.review-top {
  position: relative;
  z-index: 40;
  margin: calc(-1 * var(--space-2)) 0;
  padding: var(--space-2) 0;
  background: color-mix(in srgb, var(--bg-primary) 88%, transparent);
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
}

.review-top.is-sticky {
  position: sticky;
  top: var(--review-stick);
}

/* What the browser scrolls to in the list (a row the keys move to) comes out from under
   the bar too. Not the page's scroll padding: that held the bar's own chips behind it,
   and the browser scrolled the page each time one was focused. */
#view-transactions .review-body * {
  scroll-margin-top: var(--review-top-h, 0px);
}

/* Stuck, the bar's own controls sit nearer the navbar than the page's scroll padding
   allows for (base.css), so a focused one scrolled the page a few pixels. */
.review-top * {
  scroll-margin-top: -1.25rem;
}

/* On a phone the bar runs to the screen's edges, past the page gutter, as the contents
   rows do. */
@media (max-width: 600px) {
  .review-top {
    margin-inline: -0.75rem;
    padding-inline: 0.75rem;
  }
}

/* The search, on a row of its own, then the filters: one row of chips. On a narrow
   screen, or a touch screen, the row scrolls sideways rather than stacking into a block
   of controls above the list, and the search is in reach without scrolling it. */
.review-filters {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.review-chips {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  overflow-x: auto;
  scrollbar-width: none;
  margin: 0 calc(-1 * var(--space-1));
  padding: var(--space-1);
}

.review-chips::-webkit-scrollbar {
  display: none;
}

/* Wider up, with a mouse to reach them, the chips wrap rather than hide past the
   edge of a row that scrolls. */
@media (min-width: 769px) and (hover: hover) {
  .review-filters {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin: 0 calc(-1 * var(--space-1));
    padding: var(--space-1);
  }

  .review-chips {
    display: contents;
  }
}

.chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 36px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-full);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink-2);
  font: 500 var(--fs-meta)/1 var(--font-text);
  white-space: nowrap;
  cursor: pointer;
}

.chip:hover {
  color: var(--ink);
}

.chip[aria-pressed="true"],
.chip.is-on {
  border-color: var(--ink);
  /* The colour alone: a chip-select's caret is its background image. */
  background-color: var(--sunk);
  color: var(--ink);
  font-weight: 600;
}

/* A chip and, while it narrows the list, the cross after it that clears it in one press:
   one pill split in two, each half its own button. The cross is the chip's full height and
   at least as wide, 44pt each way on a touch screen, so a finger finds it without opening
   the chip's menu. */
.chip-pair {
  flex: none;
  display: inline-flex;
}

.chip-pair:has(> .chip-clear:not([hidden])) > .chip {
  border-right-width: 0;
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

.chip-clear {
  display: inline-grid;
  place-items: center;
  min-width: 36px;
  min-height: 36px;
  padding: 0 10px 0 8px;
  border: 1px solid var(--ink);
  border-left-color: var(--line-strong);
  border-radius: 0 var(--radius-full) var(--radius-full) 0;
  background: var(--sunk);
  color: var(--ink-2);
  cursor: pointer;
}

.chip-clear[hidden] {
  display: none;
}

.chip-clear:hover {
  color: var(--ink);
  background: color-mix(in srgb, var(--ink) 8%, var(--sunk));
}

.chip-clear svg {
  width: 14px;
  height: 14px;
}

@media (pointer: coarse), (max-width: 600px) {
  .chip-clear {
    min-height: 44px;
  }
}

@media (pointer: coarse) {
  .chip-clear {
    min-width: 44px;
  }
}

/* Account, category and the status chips: a chip showing the choice, with a caret; it
   opens the filters' menu below (.filter-menu). Positioned, so its hidden "Account:" stays
   inside it rather than past the edge of the row that scrolls. */
/* How many a status chip holds, beside its name; empty when none. */
.chip-count {
  font: 500 var(--fs-label) / 1 var(--font-mono);
  color: var(--ink-3);
}

.chip-count:empty {
  display: none;
}

.chip[aria-pressed="true"] .chip-count {
  color: var(--ink-2);
}

.chip-select {
  position: relative;
  max-width: 12rem;
  padding-right: 2rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238a8f98' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.8rem center;
  background-size: 10px 6px;
}

.chip-select[aria-expanded="true"] {
  border-color: var(--ink);
  color: var(--ink);
}

.chip-select-text {
  overflow: hidden;
  text-overflow: ellipsis;
  /* The font's own height, tails of g and y and all, which the chip's line height, 1, is
     short of where the text clips its overflow. The chip's 36px leaves it room. */
  line-height: normal;
}

/* A label that ends in what tells a same-named account apart (choiceLabelHTML): where it
   doesn't fit, the name is cut short and the detail stays whole. */
.choice-split {
  display: flex;
  min-width: 0;
}

.choice-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.choice-detail {
  flex: none;
  white-space: pre;
}

/* The choice menu: a card under its chip, placed by placeChoiceMenu (js/filtermenu.js);
   on a phone, a sheet from the bottom over a dimmed screen. Over dialogs, since the rule
   editor opens it too. */
.filter-menu-backdrop {
  position: fixed;
  inset: 0;
  z-index: 110;
}

.filter-menu-backdrop[hidden],
.filter-menu[hidden] {
  display: none;
}

.filter-menu {
  position: fixed;
  z-index: 111;
  display: flex;
  flex-direction: column;
  width: 18rem;
  max-width: calc(100vw - 2 * var(--space-4));
  max-height: 440px;
  padding: var(--space-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  animation: popoverSlide 0.18s cubic-bezier(0.16, 1, 0.3, 1);
}

.filter-menu[data-size="narrow"] {
  width: 16rem;
}

/* Named on the phone's sheet; under the chip, the chip names it. */
.filter-menu-title {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.filter-menu-search {
  flex: none;
  min-height: 36px;
  margin-bottom: var(--space-2);
  padding: 0 var(--space-3);
  border-radius: var(--radius-full);
}

.filter-menu-search[hidden] {
  display: none;
}

.filter-menu-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.filter-menu-option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 36px;
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--ink-2);
  font: 500 var(--fs-body-sm)/1.25 var(--font-text);
  text-align: left;
  cursor: pointer;
}

.filter-menu-option:hover,
.filter-menu-option:focus-visible {
  background: var(--sunk);
  color: var(--ink);
}

.filter-menu-option:focus-visible {
  outline: 2px solid var(--accent-blue);
  outline-offset: -2px;
}

.filter-menu-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A group's line starts a block, set apart by a rule and its weight; its categories sit
   in under it. */
.filter-menu-option.is-group {
  margin-top: var(--space-1);
  border-top: 1px solid var(--line);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  color: var(--ink);
  font-weight: 600;
}

.filter-menu-option.is-sub {
  padding-left: var(--space-6);
}

.filter-menu-hint {
  flex: none;
  font-size: var(--fs-label);
  color: var(--ink-3);
}

/* A group of choices under a heading of its own; a rule sets each group after the first
   apart. */
.filter-menu-group + .filter-menu-group {
  margin-top: var(--space-1);
  padding-top: var(--space-1);
  border-top: 1px solid var(--line);
}

.filter-menu-heading {
  margin: 0;
  padding: var(--space-2) var(--space-3) var(--space-1);
  font: 600 var(--fs-label) / 1.2 var(--font-text);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* A choice with a line under its name saying what it is. */
.filter-menu-option.has-note {
  padding-block: var(--space-2);
}

.filter-menu-text {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.filter-menu-note {
  font: 400 var(--fs-meta) / 1.3 var(--font-text);
  color: var(--ink-3);
}

.filter-menu-check {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--accent);
  visibility: hidden;
}

.filter-menu-option[aria-selected="true"] {
  color: var(--ink);
  font-weight: 600;
}

.filter-menu-option[aria-selected="true"] .filter-menu-check {
  visibility: visible;
}

/* In a menu that takes several choices, a box to tick leads each one. */
.filter-menu-box {
  flex: none;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border: 1.5px solid var(--line-strong);
  border-radius: 4px;
}

.filter-menu-box .filter-menu-check {
  width: 12px;
  height: 12px;
  color: var(--surface);
}

.filter-menu-option[aria-selected="true"] .filter-menu-box {
  border-color: var(--accent);
  background: var(--accent);
}

.filter-menu-done {
  flex: none;
  align-self: flex-end;
  margin-top: var(--space-2);
}

.filter-menu-done[hidden] {
  display: none;
}

.filter-menu-none {
  margin: 0;
  padding: var(--space-3);
  font-size: var(--fs-meta);
  color: var(--ink-3);
}

@media (pointer: coarse) {
  .filter-menu-option {
    min-height: 44px;
  }
}

@media (max-width: 600px) {
  .filter-menu-backdrop {
    background: var(--bg-scrim);
  }

  .filter-menu,
  .filter-menu[data-size="narrow"] {
    top: auto;
    right: 0;
    bottom: 0;
    left: 0;
    width: auto;
    max-width: none;
    max-height: min(75dvh, calc(100dvh - var(--space-12)));
    padding: var(--space-3) var(--space-4) calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
    border-width: 1px 0 0;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    box-shadow: var(--shadow-sheet);
    animation: sheetRise var(--dur) var(--ease-out);
  }

  /* As a sheet it leaves like the dialogs' sheets (components.css): the dimming fades,
     the sheet drops, and neither takes a press on its way out. */
  .filter-menu {
    transition: opacity var(--dur) var(--ease-out), translate var(--dur) var(--ease-out),
      display var(--dur) allow-discrete;
  }

  /* --scrim-shown is how much of the dimming a sheet dragged down leaves (sheetdrag.js),
     which follows the finger with no easing of its own. */
  .filter-menu-backdrop {
    opacity: var(--scrim-shown, 1);
    transition: opacity var(--fade-dur) var(--ease-fade), display var(--fade-dur) allow-discrete;
  }

  .filter-menu-backdrop.is-dragging {
    transition: none;
  }

  .filter-menu-backdrop[hidden],
  .filter-menu[hidden] {
    opacity: 0;
    pointer-events: none;
  }

  .filter-menu[hidden] {
    translate: 0 24px;
  }

  @starting-style {
    .filter-menu-backdrop:not([hidden]) {
      opacity: 0;
    }
  }

  /* The grab handle, then the menu's name. */
  .filter-menu::before {
    content: "";
    flex: none;
    width: 36px;
    height: 5px;
    margin: 0 auto var(--space-3);
    border-radius: var(--radius-full);
    background: var(--line-strong);
  }

  .filter-menu-title {
    position: static;
    width: auto;
    height: auto;
    overflow: visible;
    clip-path: none;
    margin: 0 0 var(--space-3);
    font: 600 var(--fs-body)/1.2 var(--font-text);
    color: var(--ink);
  }

  .filter-menu-done {
    align-self: stretch;
    min-height: 44px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .filter-menu {
    animation: none;
  }
}

.review-search-row {
  display: flex;
  gap: var(--space-2);
}

@media (min-width: 769px) and (hover: hover) {
  .review-search-row {
    flex: 1 0 100%;
  }
}

.review-search-box {
  position: relative;
  flex: 1;
  min-width: 0;
}

/* Room on the right for the clear button, which stands in for the browser's own: Safari
   on an iPhone draws none. */
.review-search {
  width: 100%;
  min-height: 36px;
  padding: 0 calc(var(--space-1) + 36px) 0 var(--space-3);
  border-radius: var(--radius-full);
}

.review-search::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
  display: none;
}

/* A grey disc with a cross, filling the field's height to be easy to hit. */
.review-search-clear {
  position: absolute;
  top: 0;
  right: var(--space-1);
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: none;
  color: var(--ink-3);
  cursor: pointer;
}

.review-search-clear svg {
  width: 20px;
  height: 20px;
  padding: 5px;
  border-radius: var(--radius-full);
  background: currentColor;
  stroke: var(--surface);
}

.review-search-clear:hover {
  color: var(--ink-2);
}

/* Empty, the field has nothing to clear and its placeholder has the room. */
.review-search:placeholder-shown + .review-search-clear {
  display: none;
}

.review-search:placeholder-shown {
  padding-right: var(--space-3);
}

/* The phone's Filters button (below): how many filters narrow the list, on its corner. */
.review-filters-toggle {
  display: none;
  position: relative;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--ink-2);
  cursor: pointer;
}

.review-filters-toggle svg {
  width: 20px;
  height: 20px;
}

.review-filters-toggle[aria-expanded="true"],
.review-filters-toggle.is-on {
  border-color: var(--ink);
  background: var(--sunk);
  color: var(--ink);
}

.review-filters-count {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--text-on-accent);
  font: 600 11px/18px var(--font-mono);
  text-align: center;
}

.review-filters-count:empty {
  display: none;
}

/* On a phone the chips fold behind the Filters button beside the search, to keep the
   bar at the top to one row; once opened they stay until it is pressed again or the list
   is scrolled on (foldReviewFiltersOnScroll), so the list never moves under a finger
   choosing one. Folded, the chips that are on stay out,
   in a row under the search, each with its cross, so what narrows the list is never
   hidden behind the button. */
@media (max-width: 600px) {
  .review-filters-toggle {
    display: inline-flex;
  }

  .review-filters:not(.is-open) .review-chips:not(:has(.chip.is-on:not([hidden]))),
  .review-filters:not(.is-open) .review-chips .chip-pair:not(:has(> .chip.is-on)) {
    display: none;
  }

  /* Typing, the field has the row to itself; the button's count says filters are on. */
  .review-filters.is-searching:not(.is-open) .review-chips {
    display: none;
  }

  /* Folded with chips on, the bar stays one row: the search folds to a pill before them,
     which stays put at the left as they scroll, and the Filters button at the right. */
  .review-filters:not(.is-open):not(.is-searching):has(.review-chips .chip.is-on:not([hidden])) {
    flex-direction: row;
    align-items: center;
    gap: var(--space-2);
  }

  .review-filters:not(.is-open):not(.is-searching):has(.review-chips .chip.is-on:not([hidden])) .review-search-row {
    display: contents;
  }

  .review-filters:not(.is-open):not(.is-searching):has(.review-chips .chip.is-on:not([hidden])) .review-search-box {
    display: none;
  }

  .review-filters:not(.is-open):not(.is-searching):has(.review-chips .chip.is-on:not([hidden])) .review-search-chip {
    display: inline-flex;
  }

  /* The room kept for the chips' focus rings overlaps the bar's padding, for the row to
     be as tall as the field's. */
  .review-filters:not(.is-open):not(.is-searching):has(.review-chips .chip.is-on:not([hidden])) .review-chips {
    flex: 1;
    min-width: 0;
    order: 1;
    margin-block: calc(-1 * var(--space-1));
  }

  .review-filters:not(.is-open):not(.is-searching):has(.review-chips .chip.is-on:not([hidden])) .review-filters-toggle {
    order: 2;
  }
}

.review-search-chip {
  display: none;
}

/* Bare, the magnifier is a round button the size of the Filters one. */
.review-search-chip .chip {
  padding-inline: 11px;
}

.review-search-chip .chip:not(.is-on) {
  justify-content: center;
  min-width: 44px;
}

.review-search-chip .chip svg {
  flex: none;
  width: 20px;
  height: 20px;
}

.review-search-chip .chip.is-on svg {
  width: 16px;
  height: 16px;
}

/* A long search is cut short, to leave the chips after it the room. */
.review-search-chip .chip-select-text {
  max-width: 5.5em;
}

.review-search-chip .chip-select-text:empty {
  display: none;
}

/* The date range: two date fields under the chip. */
.review-dates {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
}

.review-dates[hidden] {
  display: none;
}

.review-date-presets {
  display: flex;
  flex: 1 0 100%;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.review-dates label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--fs-label);
  color: var(--ink-3);
}

.review-dates input {
  min-height: 36px;
}

/* What Filed by the app and Catch up are, over their lists (renderListTitle). */
.review-section-note {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-meta);
  color: var(--ink-3);
}

.review-list-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-3);
}

.review-list-note > .review-section-note {
  margin: 0;
}

.review-list-note[hidden],
.review-list-note > [hidden] {
  display: none;
}

/* One day. */
.tx-day {
  margin-top: var(--space-6);
}

/* Just under the list's heading, the first day needs no space of its own. */
#tx-list > .tx-day:first-child {
  margin-top: var(--space-2);
}

.tx-day-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 36px;
  padding: 0 var(--space-1) var(--space-2);
}

.tx-day-title {
  margin: 0;
  font: 600 var(--fs-label)/1.2 var(--font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ink-3);
}

.tx-day-count {
  font: 500 var(--fs-label)/1 var(--font-mono);
  color: var(--ink-3);
}

.tx-day-title + .tx-day-count::before {
  content: "· ";
}

/* The rows. A list holds rows and nothing else, so the overview can hold the same
   rows in a <ul class="tx-list"> of its own. */
.tx-list {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.tx-row {
  position: relative;
  outline: none;
}

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

.tx-row-main {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 60px;
  padding: var(--space-2) var(--space-4);
  background: var(--surface);
}

/* The row's surface answers a press at once and eases back when the finger or button
   lets go. */
.tx-row-main {
  transition: background-color var(--dur) var(--ease-out);
}

/* A row that needs the person, in a list by day that mixes them with the rest: a faint
   amber wash and an amber edge, in the place the cursor's bar takes, and a Needs you tag
   leading its line, where it never shortens the merchant. The cursor, hover and a press
   keep their own looks over it. */
.tx-row.is-needs-you .tx-row-main {
  background: color-mix(in srgb, var(--attn-soft) 45%, var(--surface));
  box-shadow: inset 3px 0 0 var(--attn);
}

.tx-row-line {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

.tx-row-line > .tx-row-meta {
  min-width: 0;
}

.tx-row-flag {
  flex: none;
  padding: 0 7px;
  border: 1px solid var(--attn-line);
  border-radius: var(--radius-full);
  background: var(--attn-soft);
  color: var(--attn);
  font: 600 var(--fs-label) / 16px var(--font-text);
  white-space: nowrap;
}

@media (hover: hover) {
  .tx-row:hover .tx-row-main {
    background: color-mix(in srgb, var(--sunk) 45%, var(--surface));
  }
}

/* The tint and bar say which row something is about. From 700pt they mark the row the
   pane beside the list shows (aria-current), and go when the pane is emptied. Anywhere,
   they mark the J/K cursor while the keys are in use (:focus-visible); a row focused
   after a click or tap closed its detail is not marked. */
@media (min-width: 700px) {
  .tx-row[aria-current="true"] .tx-row-main {
    background: var(--accent-soft);
    box-shadow: inset 3px 0 0 var(--accent);
  }
}

.tx-row.is-cursor:focus-visible .tx-row-main {
  background: var(--accent-soft);
  box-shadow: inset 3px 0 0 var(--accent);
}

.tx-row .tx-row-main:active {
  background: var(--sunk);
  transition: none;
}

/* The row J and K land on is focused; the cursor's tint and bar are its focus mark,
   inside the list's rounded edge rather than a ring cut off by it. */
.tx-row:focus-visible {
  outline: none;
}

.tx-row:focus-visible:not(.is-cursor) .tx-row-main {
  box-shadow: inset 0 0 0 2px var(--accent);
}

/* The detail takes focus as it opens so the keys reach it; the dialog itself needs
   no ring. */
.tx-modal:focus {
  outline: none;
}

/* The merchant and its line are the row's button: the button's box is stretched
   over the whole row, so a click or tap anywhere on it opens the detail, and the
   action buttons sit above it. */
.tx-row-open {
  flex: 1;
  align-self: stretch;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.tx-row-open::after {
  content: "";
  position: absolute;
  inset: 0;
}

.tx-row-open:focus-visible {
  outline: none;
}

.tx-row-open:focus-visible::after {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.tx-row-name,
.tx-row-meta {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tx-row-name {
  font-size: var(--fs-body-sm);
  font-weight: 600;
  color: var(--ink);
}

.tx-row-meta {
  font-size: var(--fs-meta);
  color: var(--ink-3);
}

.tx-row-note {
  color: var(--ink-2);
}

/* Why the app is asking, on a row in Needs you: amber, the colour of "needs attention". */
.tx-row-why {
  color: var(--attn);
}

/* The merchant is the row's one bold run; the amount stands apart by its place and
   its figures, so a page of rows isn't mostly bold. */
.tx-row-amt {
  flex: none;
  font-size: var(--fs-body-sm);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.tx-row.is-pending .tx-row-amt {
  color: var(--ink-3);
}

/* The monogram: initials on a colour picked from the merchant's name. White letters
   on every colour pass AA (_tests/reviewlist.test.js reads these values). */
.tx-mono {
  flex: none;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 11px;
  color: #ffffff;
  font: 500 var(--fs-meta)/1 var(--font-display);
  letter-spacing: 0.02em;
  user-select: none;
}

.tx-mono-0 { background: #8a3b2e; }
.tx-mono-1 { background: #8a4b0f; }
.tx-mono-2 { background: #5f5a14; }
.tx-mono-3 { background: #2f6630; }
.tx-mono-4 { background: #0d6657; }
.tx-mono-5 { background: #1c5a80; }
.tx-mono-6 { background: #3d4c94; }
.tx-mono-7 { background: #64408c; }
.tx-mono-8 { background: #873360; }
.tx-mono-9 { background: #4b5563; }

.tx-mono-lg {
  width: 44px;
  height: 44px;
  border-radius: 13px;
  font-size: var(--fs-body-sm);
}

/* The amount, and the row actions anchored to it, on one line. */
.tx-row-end {
  position: relative;
  display: flex;
  flex: none;
  align-items: center;
  gap: var(--space-1);
  white-space: nowrap;
}

/* Row actions, the note: always in view, after the amount, on every list
   of rows and with a mouse or a finger alike, so nothing has to be hovered to be found
   and nothing moves when it is. Opening the row is the row itself, so its separate
   button is left out. */
.tx-row-actions {
  position: relative;
  z-index: 1;
  display: flex;
  flex: none;
  gap: var(--space-1);
}

.tx-row-actions .tx-act[data-action="openTxModal"] {
  display: none;
}

/* The review list is measured for its rows' ends: narrower than this, as on a phone or
   beside an open detail, the actions go under the amount, so the merchant and its line
   keep the row's width, and the line wraps rather than cutting off the account. A
   wider list keeps each row to two lines. */
#tx-list {
  container: review-list / inline-size;
}

@container review-list (max-width: 34rem) {
  #tx-list .tx-row-end {
    flex-direction: column;
    align-items: flex-end;
  }

  #tx-list .tx-row-meta {
    white-space: normal;
    overflow-wrap: anywhere;
  }

  /* The line wraps, so the tag flows at its start like a word rather than standing in a
     column of its own. */
  #tx-list .tx-row-line {
    display: block;
  }

  #tx-list .tx-row-line > .tx-row-meta {
    display: inline;
  }

  #tx-list .tx-row-flag {
    display: inline-block;
    margin-right: 6px;
  }
}

/* A row's date in Needs you never breaks across lines. */
.tx-row-date {
  white-space: nowrap;
}

.tx-act {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--ink-2);
  cursor: pointer;
}

.tx-act:hover {
  color: var(--ink);
  background: var(--sunk);
}

.tx-act svg {
  width: 18px;
  height: 18px;
}

/* The note button on a transaction that has a note: its bubble holds lines, filled in. */
.tx-act-noted {
  color: var(--ink);
  background: var(--sunk);
}

/* Needs you and Catch up, without day headings: their rows, closer. */
.tx-day.tx-run {
  margin-top: var(--space-3);
}

/* A row's monogram is its checkbox (toggleRowSelect): a box shows in its place under a
   mouse, and on every row once any is selected; a row selected has it filled. It sits
   above the button the row is, and on a touch screen reaches out to a finger's 44pt. */
.tx-mono-select {
  position: relative;
  z-index: 1;
  flex: none;
  display: grid;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: 11px;
  background: none;
  cursor: pointer;
}

.tx-mono-select > * {
  grid-area: 1 / 1;
}

.tx-check {
  display: grid;
  place-items: center;
  border: 2px solid var(--line-strong);
  border-radius: 11px;
  background: var(--surface);
  color: transparent;
  opacity: 0;
  transition: opacity var(--dur) var(--ease-out), background-color var(--dur) var(--ease-out);
}

.tx-check svg {
  width: 20px;
  height: 20px;
}

@media (hover: hover) {
  .tx-mono-select:hover .tx-check {
    opacity: 1;
  }
}

.tx-mono-select:focus-visible .tx-check,
.review.is-selecting .tx-check {
  opacity: 1;
}

.tx-mono-select[aria-checked="true"] .tx-check {
  opacity: 1;
  border-color: var(--accent);
  background: var(--accent);
  color: var(--text-on-accent);
}

@media (pointer: coarse) {
  .tx-mono-select {
    width: 44px;
    height: 44px;
    margin: -4px;
    padding: 4px;
  }
}

/* While rows are selected: a bar at the top of the list, the list's width, that sticks
   under the filters as the list scrolls, a card like the rows'. All shown and the count
   at its start, over the rows' boxes; what can be done with them; the cross that stops.
   As it comes and goes the page scrolls by its height, so no row moves (renderSelection).
   A phone puts the buttons on a line of their own. */
.review-selectbar {
  position: sticky;
  top: calc(var(--review-stick) + var(--review-top-h, 0px) + var(--space-2));
  z-index: 30;
  display: grid;
  grid-template-columns: auto 1fr auto auto auto;
  grid-template-areas: "all count file mark close";
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.review-selectbar[hidden] {
  display: none;
}

/* Room at the list's end, however short the list, so the page can scroll by the bar's
   height as it comes. */
.review.is-selecting .review-body {
  min-height: 100dvh;
}

.review-select-all {
  grid-area: all;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 36px;
  font-size: var(--fs-meta);
  color: var(--ink-2);
  white-space: nowrap;
  cursor: pointer;
}

.review-select-all input {
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--accent);
}

.review-bulk-count {
  grid-area: count;
  font-size: var(--fs-meta);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}

#btn-bulk-file {
  grid-area: file;
}

#btn-bulk-review {
  grid-area: mark;
}

.review-selectbar-close {
  grid-area: close;
}

@media (pointer: coarse) {
  .review-select-all,
  .review-selectbar-btn {
    min-height: 44px;
  }
}

@media (max-width: 600px) {
  .review-selectbar {
    grid-template-columns: auto 1fr 1fr auto;
    grid-template-areas: "all count count close" "file file mark mark";
    padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
  }

  .review-selectbar-btn {
    width: 100%;
  }
}

/* A change confirmed on a row (uikit's flashSuccess) washes the row's own surface:
   the <li> behind it is covered, as a table row is by its cells. */
.tx-row.flash-success {
  animation: none;
  border-color: var(--line) !important;
}

.tx-row.flash-success .tx-row-main {
  --flash-row-green: color-mix(in srgb, var(--accent) 24%, transparent);
  animation: flashRowWash 1.5s ease forwards;
}

/* A filed row leaves Needs you, Catch up or Filed by the app: it slides out and its space
   closes. */
.tx-row.is-leaving {
  overflow: hidden;
  transition: max-height var(--dur) var(--ease-out) 60ms, opacity var(--dur) ease-out;
  max-height: 0;
  opacity: 0;
}

.tx-row.is-leaving .tx-row-main {
  transition: transform var(--dur) var(--ease-out);
  transform: translateX(-100%);
}

@media (prefers-reduced-motion: reduce) {
  .tx-row.is-leaving,
  .tx-row.is-leaving .tx-row-main,
  .review-meter-fill {
    transition: none;
  }
}

@media (pointer: coarse), (max-width: 600px) {
  .review-search {
    padding-right: calc(var(--space-1) + 44px);
  }

  .review-search-clear {
    width: 44px;
  }

  .chip,
  .review-search,
  .review-dates input,
  .tx-cat-search,
  .filter-menu-search {
    min-height: 44px;
  }

  .tx-row-main {
    min-height: 64px;
  }
}

/* A finger needs 44pt each way: a one-word chip ("All") is narrower than that, and the
   row actions are sized for a mouse. */
@media (pointer: coarse) {
  .chip {
    min-width: 44px;
    justify-content: center;
  }

  .tx-act {
    width: 44px;
    height: 44px;
  }
}

/* Loading: a day's heading over a list of rows, in the list's own frame
   (drawReviewSkeleton in transactions.js). */
.skel-day {
  margin-top: var(--space-6);
}

.skel-day-head {
  display: flex;
  align-items: center;
  min-height: 36px;
  padding: 0 var(--space-1) var(--space-2);
  font-size: var(--fs-label);
}

.skel-list {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.skel-list .skel-row {
  min-height: 60px;
}

/* Show more, and the count that says how much is left. */
.review-more {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-6);
  font-size: var(--fs-meta);
  color: var(--ink-3);
}

.review-more .btn[hidden] {
  display: none;
}

.review-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-6);
  padding: var(--space-12) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  text-align: center;
}

.review-empty-icon {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  margin-bottom: var(--space-2);
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent);
}

.review-empty-icon svg {
  width: 24px;
  height: 24px;
}

.review-empty-title {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--ink);
  margin: 0;
}

.review-empty-text {
  margin: 0;
  font-size: var(--fs-body-sm);
  color: var(--ink-2);
}

.review-empty-action {
  margin-top: var(--space-2);
}

/* The keyboard hint: only where there is a keyboard and a mouse. */
.review-keys {
  display: none;
  margin-top: var(--space-4);
  text-align: center;
  font-size: var(--fs-label);
  color: var(--ink-3);
}

@media (hover: hover) and (pointer: fine) {
  .review-keys {
    display: block;
  }
}

.review-keys kbd,
.tx-pane-keys kbd {
  display: inline-block;
  min-width: 1.4em;
  padding: 1px 4px;
  border: 1px solid var(--line-strong);
  border-bottom-width: 2px;
  border-radius: 4px;
  background: var(--surface);
  color: var(--ink-2);
  font: 500 var(--fs-label)/1.3 var(--font-mono);
  text-align: center;
}

/* The Undo toast. Above the tab bar on a phone; at the top while a bottom sheet is
   up, so it never covers the sheet's buttons.

   Centred by layout (both edges pinned, auto margins, its own width), which leaves
   transform free for the motion: it rises into place, drops in from the top over a
   sheet, and gets a nudge from js/transactions.js when a second review replaces its
   words. Transform and opacity alone, so the motion runs off the main thread while the
   lists redraw. */
.review-toast {
  position: fixed;
  left: 0;
  right: 0;
  bottom: var(--space-6);
  width: fit-content;
  margin: 0 auto;
  z-index: 150;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  max-width: calc(100vw - 2 * var(--space-4));
  padding: var(--space-1) var(--space-1) var(--space-1) var(--space-4);
  border-radius: var(--radius-full);
  background: var(--ink);
  color: var(--paper);
  box-shadow: var(--shadow-sheet);
  font-size: var(--fs-body-sm);
  animation: toastRise var(--dur) var(--ease-out);
}

@keyframes toastRise {
  from { transform: translateY(16px) scale(0.96); opacity: 0; }
}

@keyframes toastDrop {
  from { transform: translateY(-16px) scale(0.96); opacity: 0; }
}

.review-toast[hidden] {
  display: none;
}

.review-toast-text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 768px) {
  .review-toast {
    bottom: calc(4.25rem + env(safe-area-inset-bottom, 0px));
  }
}

@media (max-width: 600px) {
  .modal-open .review-toast {
    top: calc(var(--space-3) + env(safe-area-inset-top, 0px));
    bottom: auto;
    animation-name: toastDrop;
  }

  .review-title {
    font-size: 1.75rem;
  }

  .review-head {
    align-items: flex-start;
  }

  .tx-row-main {
    padding: var(--space-2) var(--space-3);
  }

  .tx-day,
  .skel-day {
    margin-top: var(--space-4);
  }

  .skel-list .skel-row {
    padding-inline: var(--space-3);
  }
}

@media (prefers-reduced-motion: reduce) {
  .review-toast {
    animation: none;
  }
}

/* ============================================================================
   Transaction detail

   A bottom sheet on a phone (components.css turns every dialog into one), a centred
   dialog wider up. Merchant and amount first, then category as one-tap chips, the
   note with its Save note. A pick saves at once; there are no buttons at its foot.
   ============================================================================ */
.tx-modal {
  position: relative;
  max-width: 520px;
  /* Clipped, so nothing inside ever gives the sheet or the pane a sideways scrollbar. */
  overflow-x: clip;
}

.tx-detail-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.tx-detail-who,
.tx-detail-heading {
  flex: 1;
  min-width: 0;
}

.tx-detail-who {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.tx-detail-who .modal-title {
  margin: 0;
  overflow-wrap: anywhere;
}

.tx-detail-sub {
  margin-top: 2px;
  font-size: var(--fs-meta);
  color: var(--ink-3);
}

/* The provider's other texts for the transaction, each under a small label, in full:
   the bank's own words are what tells two "To" payments apart. */
.tx-detail-texts {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-meta);
  color: var(--ink-2);
}

.tx-detail-texts > div {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-2);
}

.tx-detail-texts dt {
  color: var(--ink-3);
}

.tx-detail-texts dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.tx-detail-head .modal-close {
  align-self: flex-start;
  min-width: 44px;
  min-height: 44px;
  margin: calc(-1 * var(--space-2)) calc(-1 * var(--space-2)) 0 0;
}

.tx-detail-amount {
  margin: var(--space-4) 0 0;
  font-family: var(--font-display);
  font-size: var(--fs-hero-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

/* Money out is drawn in ink elsewhere (amount-out); as a headline figure, ink-figure. */
.tx-detail-amount.amount-out { color: var(--ink-figure); }

.tx-detail-label {
  display: block;
  margin: var(--space-6) 0 var(--space-2);
  font: 600 var(--fs-label)/1.2 var(--font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.tx-cats {
  display: flex;
  flex-wrap: wrap;
  /* The height held for the rows (fitCategoryRows) never stretches the chips. */
  align-content: flex-start;
  gap: var(--space-2);
}

:is(.tx-cat, .tx-cat-placeholder) .tx-cat-key {
  display: none;
  font: 500 var(--fs-label)/1 var(--font-mono);
  color: var(--ink-3);
}

@media (hover: hover) and (pointer: fine) {
  :is(.tx-cat, .tx-cat-placeholder) .tx-cat-key {
    display: inline;
  }
}

/* A pending hold's category, the only chip it shows: drawn as chosen, but nothing to press. */
.tx-cats .tx-cat-own,
.tx-cat[aria-checked="true"] {
  border-color: var(--ink);
  background: var(--sunk);
  color: var(--ink);
  font-weight: 600;
}

.tx-cats .tx-cat-own {
  cursor: default;
}

/* The app's guess for a transaction it is asking about (keepsAGuess in txdialog.js): not
   chosen yet, so not drawn as chosen, but offered to keep, with a dashed accent outline and
   "Keep" after its name. Tapping it files it, and it is drawn as chosen. The blank chip
   that holds its room while the choices load has the same "Keep", unseen, so the row
   doesn't change width when they arrive. */
.tx-cat.is-guess {
  border-style: dashed;
  border-color: var(--accent);
  color: var(--ink);
}

:is(.tx-cat, .tx-cat-placeholder).is-guess::after {
  content: "Keep";
  /* Said by the chip's own description, not read out twice. */
  content: "Keep" / "";
  font: 600 var(--fs-label)/1 var(--font-text);
  color: var(--accent);
}

.tx-cat-placeholder.is-guess::after {
  color: transparent;
}

/* A category saved: the chip that was tapped blooms a ring and its border eases from
   the accent back to the selected ink, where the eyes already are (glowCategoryChip in
   txdialog.js), and so does a choice picked from Browse all or the search. Its own ring
   rather than flash-success, which forces a green border and leaves a card's shadow
   behind, both wrong on a pill. */
@keyframes catSaved {
  0%   { border-color: var(--accent); box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  25%  { border-color: var(--accent); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 22%, transparent), 0 0 18px 2px color-mix(in srgb, var(--accent) 30%, transparent); }
  100% { box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent; }
}

.tx-cat.is-saved,
.tx-cat-pick.is-saved,
.tx-cat-tile.is-saved {
  animation: catSaved 1.1s cubic-bezier(0.16, 1, 0.3, 1);
}

@media (prefers-reduced-motion: reduce) {
  .tx-cat.is-saved,
  .tx-cat-pick.is-saved,
  .tx-cat-tile.is-saved {
    animation: none;
  }
}

.tx-cat:disabled {
  cursor: default;
  opacity: 0.6;
}

/* A pick on its way to the server (renderModalFiling in txdialog.js): every other choice
   greys out and takes no tap until the answer is in; the one picked stays as it is, then
   glows. The grey waits a beat, so an answer that comes at once shows only the glow. */
.is-filing :is(.tx-cat:not([aria-checked="true"], [aria-pressed="true"]), .tx-cat-pick:not([aria-current="true"]), .tx-cat-tile:not([aria-current="true"])) {
  cursor: progress;
  opacity: 0.5;
  transition: opacity 150ms ease 100ms;
}

/* On the merchant's chips: how many of its others were filed there. */
.tx-cat-count {
  font: 500 var(--fs-label)/1 var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

/* Under the Category label: why the app is asking, or where the category came from. */
.tx-detail-why {
  margin: calc(-1 * var(--space-1)) 0 var(--space-2);
  font-size: var(--fs-meta);
  color: var(--ink-3);
}

.tx-detail-why.is-asking {
  color: var(--attn);
}

/* While the merchant's history loads, blank chips the size of the suggestions hold their
   room (renderModalCategories in txdialog.js). */
.tx-cat-placeholder,
.tx-cat-placeholder:hover,
.tx-cat-placeholder .tx-cat-key {
  color: transparent;
  cursor: default;
}

.tx-cat-placeholder {
  background: var(--sunk);
}

.tx-cat-placeholder.is-current {
  font-weight: 600;
}

/* Under the suggestions: a search, and Browse all beside it. */
/* The detail keeps a pick's spot under the finger itself (keepInPlace in txdialog.js);
   the browser anchoring its scroll too moved the note by the change above it. */
#tx-detail-modal .modal,
#tx-pane {
  overflow-anchor: none;
}

.tx-cat-tools {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.tx-cat-tools[hidden] {
  display: none;
}

.tx-cat-search {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 36px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-full);
}

/* On its own row, nothing beside it stretches it to a finger's height. */
@media (pointer: coarse), (max-width: 600px) {
  .tx-cat-search {
    min-height: 44px;
  }
}

.tx-cat-more[aria-expanded="true"] {
  border-color: var(--ink);
  background: var(--sunk);
  color: var(--ink);
  font-weight: 600;
}

/* What the search finds, or the groups to browse: a sunk tray under the tools. */
.tx-cat-all {
  margin-top: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--sunk);
}

.tx-cat-all[hidden] {
  display: none;
}

/* How the merchant was filed before: one line, and the latest few under it. */
.tx-cat-history {
  margin-top: var(--space-3);
  font-size: var(--fs-meta);
  line-height: 1.4;
}

.tx-cat-history[hidden] {
  display: none;
}

.tx-cat-history-line {
  margin: 0;
  color: var(--ink-2);
}

.tx-cat-history-list {
  margin: 0;
  padding: 0;
  list-style: none;
  color: var(--ink-3);
}

.tx-cat-groups {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  gap: var(--space-2);
}

/* One group: its name, and a line of what is in it. */
.tx-cat-tile {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  text-align: left;
  font: inherit;
  cursor: pointer;
}

/* A group with no subcategories (Utilities) has no second line to make it tall enough. */
@media (pointer: coarse) {
  .tx-cat-tile {
    min-height: 44px;
    justify-content: center;
  }
}

.tx-cat-tile:hover {
  border-color: var(--line-strong);
}

.tx-cat-tile[aria-current="true"] {
  border-color: var(--ink);
}

.tx-cat-tile-name {
  font: 600 var(--fs-body-sm)/1.3 var(--font-text);
  color: var(--ink);
}

.tx-cat-tile-subs {
  overflow: hidden;
  font-size: var(--fs-label);
  line-height: 1.35;
  color: var(--ink-3);
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* One group open: the way back, its name, then its categories. */
.tx-cat-browse-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.tx-cat-back {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  min-height: 32px;
  padding: 0 var(--space-2) 0 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: 500 var(--fs-meta)/1 var(--font-text);
  cursor: pointer;
}

@media (pointer: coarse), (max-width: 600px) {
  .tx-cat-back {
    min-height: 44px;
  }
}

.tx-cat-back svg {
  width: 16px;
  height: 16px;
}

.tx-cat-browse-name {
  font: 600 var(--fs-body-sm)/1.2 var(--font-text);
  color: var(--ink);
}

.tx-cat-group,
.tx-cat-found {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* What the search found, under each group's name. */
.tx-cat-found + .tx-cat-found {
  margin-top: var(--space-3);
}

.tx-cat-found-name {
  flex-basis: 100%;
  font: 600 var(--fs-label)/1.2 var(--font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* The group itself, set apart by its weight. */
.tx-cat-pick-group {
  font-weight: 600;
  color: var(--ink);
}

.tx-cat-pick[aria-current="true"] {
  border-color: var(--ink);
  background: var(--sunk);
}

/* Copies of the transaction hidden as duplicates. A quiet panel in the detail rather than
   a dialog over it. */
.tx-ask {
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--sunk);
}

.tx-ask-text {
  margin: 0;
  font-size: var(--fs-body-sm);
  font-weight: 600;
  color: var(--ink);
}

.tx-ask-list {
  margin: var(--space-2) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-meta);
  color: var(--ink-2);
}

/* A copy hidden as a duplicate: its date, amount and the bank's words, and the way to put
   it back beside them, wrapping under them on a phone. */
.tx-copy {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-1) var(--space-3);
}

.tx-copy + .tx-copy {
  margin-top: var(--space-2);
}

.tx-copy-text {
  min-width: 0;
  overflow-wrap: anywhere;
}

.tx-cat-none {
  margin: var(--space-3) 0 0;
  font-size: var(--fs-meta);
  color: var(--ink-3);
}

.tx-detail-note {
  min-height: 46px;
}

/* Save note, right under the note, at its start. */
/* Save note, and Save and suggest a category beside it when suggestions are on. */
.tx-note-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

/* Lettered as the chip beside it, and the category chips above. */
.tx-note-bar .btn {
  font-size: var(--fs-meta);
}

@media (pointer: coarse), (max-width: 600px) {
  .tx-note-bar :is(.btn, .note-suggest-btn) {
    min-height: 44px;
  }
}

/* Suggest a category from the note (js/notesuggest.js): a quiet chip to ask, beside Save
   note, and the answer below as a chip that files the transaction, Claude's reason under
   it. The person turned suggestions on in Settings, which says what they send, so the
   detail no longer says it again. */
.note-suggest-result {
  margin-top: var(--space-2);
}

.note-suggest-btn {
  min-height: 32px;
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line-strong));
}

.note-suggest-btn:hover:not(:disabled) {
  color: var(--accent);
  background: var(--accent-soft);
}

.note-suggest-btn:disabled {
  cursor: default;
  color: var(--ink-3);
  border-color: var(--line);
}

.note-suggest-btn[hidden] {
  display: none;
}

:is(.note-suggest-btn, .note-suggestion-chip) svg {
  width: 14px;
  height: 14px;
  flex: none;
}

.note-suggest-result:empty {
  display: none;
}

.note-suggestion {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-1);
  padding: var(--space-3) var(--space-4);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line));
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--accent-soft) 55%, var(--surface));
}

.note-suggestion-head {
  flex-basis: 100%;
  font: 600 var(--fs-label)/1.2 var(--font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}

.note-suggestion-chip {
  max-width: 100%;
  white-space: normal;
  text-align: left;
  line-height: 1.25;
  padding-block: var(--space-1);
}

.note-suggestion-chip svg {
  color: var(--accent);
}

.note-suggestion-group {
  font-weight: 400;
  color: var(--ink-3);
}

.note-suggestion-chip:disabled {
  cursor: default;
  opacity: 1;
}

.note-suggestion-why {
  flex-basis: 100%;
  margin: 0;
  font-size: var(--fs-meta);
  color: var(--ink-2);
}

/* Leading, for a check or a payment to a person: the note is the way to the category, so
   it is roomier and asking is the accented action, with the category's own label kept
   clear of it below. */
.tx-note-section.is-lead .tx-detail-label {
  margin-top: var(--space-4);
}

.tx-note-section.is-lead .tx-detail-note {
  min-height: 64px;
}

.tx-note-section.is-lead .note-suggest-btn:not(:disabled) {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--text-on-accent);
}

.tx-note-section.is-lead .note-suggest-btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 88%, var(--ink));
  color: var(--text-on-accent);
}

/* An answer for the note as it was before an edit: kept, faded, until asked again. */
.note-suggestion.is-stale {
  opacity: 0.6;
}

.note-suggestion-error {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-meta);
  color: var(--attn);
}

/* Close under what is above it, and on a phone close to the sheet's end (below). */
.tx-bank {
  margin-top: var(--space-2);
  font-size: var(--fs-meta);
  color: var(--ink-3);
}

.tx-bank summary {
  width: fit-content;
  margin: 0 auto;
  padding: var(--space-2);
  cursor: pointer;
}

.tx-bank-body {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.tx-bank-body .form-input {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono);
}

/* The detail's sheet ends Bank details just above the home indicator rather than a gap's
   width and the indicator's room further down, as other sheets do (components.css). */
@media (max-width: 600px) {
  #tx-detail-modal:not(.tx-detail-docked) .modal {
    padding-bottom: max(var(--space-3), env(safe-area-inset-bottom, 0px));
  }
}

/* ============================================================================
   Split view

   From 700pt wide (an iPad, half a laptop) the title, progress and filters run across
   the top, and below them the list and the selected transaction sit side by side: the
   detail is the phone's bottom sheet, docked in a pane (#tx-pane) that js/txdialog.js
   moves it into, its top level with the first transaction's (--tx-pane-offset). The
   pane holds its place under the navbar and the filters while the list scrolls. It is
   there whether or not a transaction is open, a card saying what it is for when none is,
   so opening and closing one never moves the list. Narrower (a phone, the iPhone Duo
   open, whose 626pt would leave the list too narrow beside it), the pane is never shown
   and the detail opens over the list. SPLIT_VIEW_QUERY in js/reviewlist.js names the same width.
   ============================================================================ */
.tx-pane {
  display: none;
}

/* Docked, the detail is part of the pane: no card of its own and no grab handle. Its ×
   stays, at the pane's top right, and empties the pane; so does Escape. Being part of the
   pane, it has no dialog's pop: it comes into an empty pane as an account's page does
   beside its list, fading in a little from the right, and the empty pane's card fades
   back in when it closes. Moving from row to row it stays still. */
.tx-detail-docked {
  display: none;
  transition: opacity var(--fade-dur) var(--ease-fade), translate var(--dur) var(--ease-out);
}

@starting-style {
  .tx-detail-docked.active {
    opacity: 0;
    translate: 12px 0;
  }
}

.tx-detail-docked.active {
  display: block;
}

.tx-detail-docked .modal {
  width: 100%;
  max-width: none;
  max-height: none;
  overflow: visible;
  overflow-x: clip;
  margin: 0;
  padding: var(--space-6);
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  animation: none;
  transition: none;
}

.tx-detail-docked .modal::before {
  display: none;
}

@media (min-width: 700px) {
  #view-transactions.active {
    display: grid;
    /* The list never has less room than the detail. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-areas: "banner banner" "head head" "top top" "body pane";
    gap: 0 var(--space-4);
    align-items: start;
  }

  /* A load error's banner goes in ahead of the list, across both columns, above the
     filters; with none, its row has no height. */
  #view-transactions > .load-error-banner {
    grid-area: banner;
  }

  /* The screen's grid places the top and the body themselves. */
  #view-transactions.active .review {
    display: contents;
  }

  .review-head {
    grid-area: head;
    min-width: 0;
  }

  .review-top {
    grid-area: top;
    min-width: 0;
  }

  .review-body {
    grid-area: body;
    min-width: 0;
  }

  .tx-pane {
    grid-area: pane;
    margin-top: var(--tx-pane-offset, var(--space-3));
    display: block;
    position: sticky;
    top: calc(var(--review-stick) + var(--review-top-h, 0px) + var(--space-4));
    max-height: calc(100dvh - 3.75rem - 1px - var(--review-top-h, 0px) - var(--space-8));
    overflow-y: auto;
    /* Without it overflow-x follows overflow-y to auto, and the detail sliding in 12px
       from the right flashes a sideways scrollbar under it. */
    overflow-x: hidden;
    overscroll-behavior: contain;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
  }
}

/* Below a laptop's width the tab bar takes the bottom of the screen; the pane stops
   above it. */
@media (min-width: 700px) and (max-width: 768px) {
  .tx-pane {
    max-height: calc(100dvh - 3.75rem - 1px - var(--review-top-h, 0px) - 5.5rem - var(--space-4));
  }
}

@media (min-width: 700px) and (max-width: 1023px) {
  .tx-detail-docked .modal {
    padding: var(--space-4) var(--space-4) var(--space-6);
  }
}

/* A laptop and wider: the list leads and the pane keeps a dialog's width. */
@media (min-width: 1024px) {
  #view-transactions.active {
    grid-template-columns: minmax(0, 1fr) minmax(0, 28rem);
    gap: 0 var(--space-6);
    max-width: 76rem;
    margin: 0 auto;
  }
}

/* The pane with no transaction open: a card of its own, saying what it is for, how many
   transactions need the user, and, with a keyboard, how to move through them. */
.tx-pane:has(#tx-detail-modal.active) .tx-pane-empty {
  display: none;
}

.tx-pane-empty {
  display: grid;
  justify-items: center;
  gap: var(--space-2);
  padding: var(--space-8) var(--space-6);
  text-align: center;
  transition: opacity var(--fade-dur) var(--ease-fade);
}

@starting-style {
  .tx-pane-empty {
    opacity: 0;
  }
}

.tx-pane-art {
  position: relative;
  width: 9.5rem;
  height: 5.5rem;
  margin-bottom: var(--space-3);
}

/* Three transaction rows, fanned: the list's own shape, quiet. */
.tx-pane-card {
  position: absolute;
  left: 50%;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 8.5rem;
  padding: var(--space-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.tx-pane-card:nth-child(1) {
  top: 0;
  transform: translateX(-50%) rotate(-6deg) translateX(-0.6rem);
  opacity: 0.55;
}

.tx-pane-card:nth-child(2) {
  top: 1.1rem;
  transform: translateX(-50%) rotate(4deg) translateX(0.6rem);
  opacity: 0.8;
}

.tx-pane-card:nth-child(3) {
  top: 2.3rem;
  transform: translateX(-50%);
}

.tx-pane-card .tx-mono {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  font-size: 0.6rem;
}

.tx-pane-line {
  flex: 1;
  height: 0.4rem;
  border-radius: var(--radius-full);
  background: var(--sunk);
}

.tx-pane-title {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 700;
  color: var(--ink);
}

.tx-pane-text,
.tx-pane-summary {
  max-width: 18rem;
  margin: 0;
  font-size: var(--fs-meta);
  color: var(--ink-2);
}

.tx-pane-summary:empty {
  display: none;
}

.tx-pane-keys {
  display: none;
  margin: var(--space-2) 0 0;
  font-size: var(--fs-meta);
  color: var(--ink-3);
}

@media (hover: hover) and (pointer: fine) {
  .tx-pane-keys {
    display: block;
  }
}

/* The merchant the list is narrowed to, with the cross that clears it. */
.review-merchant-chip {
  max-width: 14rem;
}

.review-merchant-chip svg {
  flex: none;
  width: 14px;
  height: 14px;
}

.review-merchant-chip[hidden] {
  display: none;
}
