body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  width: 100%;
  box-sizing: border-box;
  margin: 40px 0;
  padding: 0 40px;
  color: #1a1a1a;
  background: #fafafa;
}

nav {
  margin-bottom: 30px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

nav a {
  margin-right: 16px;
  text-decoration: none;
  color: #197eab;
  font-weight: 600;
}

h1 {
  font-size: 22px;
}

table {
  width: 100%;
  border-collapse: collapse;
  background: white;
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  table-layout: fixed; /* lets dragging a header actually set that column's width */
}

/* Every filtered/tabular section of the Reports page (Net Worth, By Month,
   By Category) — its heading, filter controls, and table all travel
   together at one comfortable reading width, rather than a full-width
   header stretching its filter controls off toward the edge of a wide
   window while the table underneath stays naturally narrower. Not applied
   to the Transactions page's table, which is deliberately fluid with the
   window instead (a wide table with many resizable columns wants the
   extra room; these narrow summary tables don't).

   Also a card in its own right — same white/rounded/shadow treatment as
   the two graphs above — so each section reads as one clearly separate
   block instead of just running into the next on the plain page
   background. */
.report-section {
  max-width: 720px;
  background: white;
  border-radius: 8px;
  padding: 20px 24px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  margin-top: 24px;
}
/* The table inside already sits on the section's own white card — its own
   shadow would just stack a second one on top, so a plain hairline border
   takes its place for a single, cleaner edge. */
.report-section .report-table { box-shadow: none; border: 1px solid #eee; }
/* Rounded corners used to come from overflow:hidden on the table itself,
   but that also silently breaks position:sticky on every descendant (a
   well-known CSS interaction) — the sticky header needs this removed.
   Rounding the actual corner cells instead achieves the same look. These
   track real DOM order, so they still land on the correct cell even after
   dragging columns into a new order. */
thead tr:first-child th:first-child { border-top-left-radius: 8px; }
thead tr:first-child th:last-child { border-top-right-radius: 8px; }
tbody tr:last-child td:first-child { border-bottom-left-radius: 8px; }
tbody tr:last-child td:last-child { border-bottom-right-radius: 8px; }

.text-right { text-align: right; }

th, td {
  text-align: left;
  vertical-align: top; /* rows can now grow tall (e.g. an expanded description) — top alignment reads better than every other cell centering in that extra space */
  padding: 10px 12px;
  border-bottom: 1px solid #eee;
  /* border-box makes a header's CSS "width" match its offsetWidth (which
     already includes padding) — without this, table-tools.js setting
     style.width during a drag would make the column grow by an extra
     2×padding on top of the actual mouse movement. */
  box-sizing: border-box;
  /* A resized column needs somewhere for overflow to go — long bank
     transaction descriptions truncate with "…" rather than wrapping;
     table-tools.js adds a title attribute so hovering shows the full text. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

th {
  background: #f3f4f6;
  font-size: 13px;
  text-transform: uppercase;
  color: #666;
  /* sticky (not just relative) also anchors the resize handle to this cell —
     sticky still establishes a containing block the same way relative does —
     but additionally pins the header to the top of its scrolling container as
     you scroll, so it's still visible far down a long table. On most pages
     that container is the whole page; on the Transactions page it's the
     bounded, internally-scrolling .table-scroll box (see its comment). */
  position: sticky;
  top: 0;
  z-index: 2; /* stays above scrolling body rows, but below popovers/menus (z-index 1000) */
}

/* The header itself is no longer the sort trigger — see .sort-btn below —
   so it doesn't need its own pointer cursor or hover highlight either;
   user-select:none stays, since a header being draggable (reordering)
   still shouldn't let a click-drag select its text. */
th.sortable-header { user-select: none; }

/* The actual, deliberately small click target for sorting — a header is
   already a drag handle for column reordering and sits right next to the
   resize handle, so making the *whole* header a sort trigger too made an
   ordinary click there (to start a drag, or just by accident) too easy to
   fire as a sort. draggable=false (set in JS) keeps a click here from
   being swept into that same drag gesture. */
.sort-btn {
  background: none;
  border: none;
  padding: 2px 4px;
  margin-left: 2px;
  cursor: pointer;
  vertical-align: middle;
}
.sort-btn:hover .sort-arrow { color: #197eab; }
.sort-arrow { display: inline-block; width: 12px; color: #b3b2a6; font-size: 11px; }
.sort-arrow.active { color: #197eab; font-size: 10px; }

.col-resizer {
  position: absolute;
  top: 0;
  right: 0;
  width: 6px;
  height: 100%;
  cursor: col-resize;
}
/* A persistent thin line marks where to grab, rather than only revealing
   itself on hover — a hidden hit target isn't discoverable on its own. */
.col-resizer::after {
  content: "";
  position: absolute;
  top: 20%;
  bottom: 20%;
  left: 50%;
  width: 2px;
  transform: translateX(-50%);
  background: #c3c2b7;
  border-radius: 1px;
}
.col-resizer:hover, .col-resizer.active { background: rgba(25, 126, 171, 0.2); }
.col-resizer:hover::after, .col-resizer.active::after {
  background: #197eab;
  width: 3px;
  top: 10%;
  bottom: 10%;
}

th.reorderable-header { cursor: grab; }
th.reorderable-header.dragging { cursor: grabbing; opacity: 0.4; }
th.reorderable-header.drag-over { box-shadow: inset 3px 0 0 #197eab; }

/* Net Worth's Assets/Debt rows — same drag-to-reorder feel as the column
   headers above, but on whole <tr>s within one tbody instead of <th>s
   across a row. Just 2 plain columns (no resize/reorder — see
   no-col-tools in table-tools.js), so the drag handle and (for a custom
   item) the delete button live inline inside the name/balance cells
   instead of getting columns of their own.

   The Balance column gets a fixed width here (not table-tools' usual
   data-autofit) rather than the table-layout:fixed default of splitting
   evenly with Account/Item — half-and-half left the name stranded at the
   far left and the number stranded at the far right with a huge gap
   between them. A fixed width is also what keeps Assets and Debt sized
   identically to each other, rather than each computing its own split
   from whatever happens to be in it that day.

   The table itself is also fixed at one explicit width, rather than the
   usual table { width: 100% } (or even width: auto with a max-width —
   auto still shrink-fits to each table's OWN longest line under
   table-layout:fixed, which is exactly what had Assets, full of short
   names like "OP Bus", come out narrower than Debt, with longer ones like
   "Taxes - 2024 Federal"). Net Worth moved into a wide flex:2 card next
   to Yearly Totals (see .top-row), and a 100%-wide table there stretched
   every row out across that same 700+px card, leaving a huge empty gap
   before the Balance column. One explicit width is what makes Assets and
   Debt match each other regardless of what's actually in either one —
   same cap as the Add Item form right below, so the two read as one
   consistently-sized block. */
.net-worth-table { width: 380px; }
.net-worth-table th:last-child,
.net-worth-table td:last-child {
  width: 130px;
}
.net-worth-row { cursor: grab; }
.net-worth-row.dragging { cursor: grabbing; opacity: 0.4; }
/* display:flex directly on a <td> was the bug here before — it strips the
   cell out of the table's row/column layout entirely (browsers no longer
   treat it as a table-cell once its own display is overridden), which is
   what was stacking Account/Item and Balance as two full-width blocks
   instead of sitting side by side. inline-block on the pieces INSIDE a
   plain table-cell gets the same alignment without that side effect. */
.net-worth-name-cell .drag-handle { margin-right: 8px; }
/* Not plain text-align:right on its own — the delete button (only on a
   custom item, never an account) gets its own fixed-width slot so the
   BALANCE ITSELF always lands at the same x position whether or not a
   delete button follows it. Without this, a Debt row with a delete
   button and an Asset row without one would right-align their numbers to
   two different edges. */
.net-worth-balance-cell { text-align: right; white-space: nowrap; }
.net-worth-amount-slot { display: inline-block; text-align: right; vertical-align: middle; }
.net-worth-delete-slot { display: inline-block; width: 20px; text-align: right; vertical-align: middle; }
.net-worth-row .drag-handle { font-size: 14px; color: #bbb; }
.net-worth-delete-btn {
  background: none;
  border: none;
  color: #b84c51;
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  padding: 0;
  vertical-align: middle;
}
/* Assets and Debt side by side rather than stacked — each in its own
   column so their headings line up at the same height regardless of how
   many rows either table ends up with. flex-wrap lets Debt drop below
   Assets on a narrow window instead of squeezing two 420px tables into
   less space than that. */
.net-worth-columns { display: flex; gap: 24px; flex-wrap: wrap; }
h3.net-worth-subheading { margin-top: 20px; margin-bottom: 6px; }
h3.net-worth-subheading:first-of-type { margin-top: 16px; }

.amount-income { color: #418032; font-weight: 600; }
.amount-expense { color: #b84c51; font-weight: 600; }

.btn {
  display: inline-block;
  padding: 8px 16px;
  background: #197eab;
  color: white;
  border-radius: 6px;
  text-decoration: none;
  border: none;
  cursor: pointer;
  font-size: 14px;
}

.btn-danger { background: #b84c51; }
.btn-secondary { background: #6b7280; }

form.inline { display: inline; }

.field { margin-bottom: 16px; }
.field label { display: block; margin-bottom: 4px; font-weight: 600; font-size: 14px; }
.field input, .field select {
  width: 100%;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 6px;
  box-sizing: border-box;
}

.radio-group label { display: inline-block; font-weight: normal; margin-right: 16px; }
.radio-group input { width: auto; }

.check-field label { font-weight: normal; }
.check-field input { width: auto; margin-right: 6px; }

.actions { margin-top: 20px; }
.empty { color: #888; padding: 20px 0; }
.hint { color: #888; font-size: 13px; margin: -4px 0 10px; }

h2 { font-size: 16px; margin-top: 36px; }
h3 { font-size: 14px; margin-top: 24px; margin-bottom: 8px; color: #444; }

.stat-row { display: flex; align-items: center; gap: 16px; margin-bottom: 10px; }
/* Yearly Totals — a narrow column next to Net Worth, so its 3 stat boxes
   stack one per row instead of squeezing three-across into that width. */
.stat-row-stacked { flex-direction: column; align-items: stretch; }
.stat-box {
  flex: 1;
  background: white;
  border-radius: 8px;
  padding: 16px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.stat-label { font-size: 13px; color: #666; margin-bottom: 6px; }
.stat-value { font-size: 22px; font-weight: 700; }

/* Total Income / Total Expenses — opens the drill-down modal below. Net
   isn't clickable: it's a difference, not a filtered set of transactions,
   so there's nothing single list to show for it. */
.stat-box-clickable { cursor: pointer; }
.stat-box-clickable:hover { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 0 0 2px #197eab; }
.stat-box-clickable:focus-visible { outline: none; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 0 0 2px #197eab; }

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 64px 20px;
  z-index: 2000; /* above the row-menu/category-picker popovers (1000) and the sticky table header (2) */
  overflow-y: auto;
}
.modal {
  background: white;
  border-radius: 12px;
  width: 480px;
  max-width: 100%;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  overflow: hidden;
}
.modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 20px 14px;
}
.modal-title { font-size: 18px; font-weight: 700; }
.modal-subtitle { font-size: 13px; color: #666; margin-top: 2px; }
.modal-close {
  background: none;
  border: none;
  color: #888;
  font-size: 15px;
  cursor: pointer;
  padding: 4px;
  line-height: 1;
  flex-shrink: 0;
}
.modal-close:hover { color: #1a1a1a; }
.modal-list { max-height: 60vh; overflow-y: auto; border-top: 1px solid #eee; }
.modal-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 20px;
  border-bottom: 1px solid #eee;
  /* It's an <a> now (opens the transaction's own detail page in a new
     tab), not just a static row — reset link styling so it still reads
     as a plain row, with a hover state as the only hint it's clickable. */
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}
.modal-row:last-child { border-bottom: none; }
.modal-row:hover { background: #f9fafb; }
.modal-row-date { width: 52px; flex-shrink: 0; font-size: 12px; color: #888; }
.modal-row-main { flex: 1; min-width: 0; }
.modal-row-description {
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.modal-row-category { font-size: 12px; color: #888; }
.modal-row-amount { font-size: 14px; flex-shrink: 0; }

.split-rows { padding: 4px 20px 0; display: flex; flex-direction: column; gap: 8px; max-height: 45vh; overflow-y: auto; }
.split-row { display: grid; grid-template-columns: 1.2fr 1.2fr 110px 24px; gap: 8px; align-items: center; }
.split-remove-btn {
  background: none;
  border: none;
  color: #aaa;
  cursor: pointer;
  font-size: 13px;
  padding: 4px;
  line-height: 1;
}
.split-remove-btn:hover { color: #b84c51; }
.split-modal-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 14px 20px 0;
  font-size: 13px;
  font-weight: 600;
}
.split-remaining-balanced { color: #418032; }
.split-remaining-unbalanced { color: #b45309; }
.split-modal-error {
  margin: 0 20px 12px;
  padding: 8px 12px;
  background: #ffdfde;
  color: #b84c51;
  border-radius: 6px;
  font-size: 13px;
}
.split-modal-actions { display: flex; gap: 8px; padding: 16px 20px 20px; }

.chart-row { display: flex; gap: 16px; margin-top: 20px; }

/* Net Worth + Yearly Totals, side by side at the top of the page. Net
   Worth's own tables make it much taller than the compact Yearly Totals
   card next to it — flex-start (not the default stretch/center) keeps
   Yearly Totals pinned to the top of that height instead of centered
   partway down it. Both are .report-section cards, so their own
   margin-top would double up with this row's — zeroed out here instead. */
.top-row { display: flex; gap: 20px; align-items: flex-start; margin-top: 24px; }
.top-row > .report-section { margin-top: 0; }
.chart-box {
  flex: 1;
  max-width: 560px; /* stops each graph from stretching wider (and its doughnut chart from ballooning) on a wide screen */
  background: white;
  border-radius: 8px;
  padding: 16px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  min-width: 0; /* lets canvas shrink inside a flex child instead of overflowing */
}
/* Bigger and bolder than the site's plain 16px h2 — these are a card's
   own title, not just a run-in heading in a page of flowing text, so they
   can afford to read as the anchor of their section at a glance. Scoped to
   the Reports page's cards rather than a global h2 bump, since elsewhere
   (Categories, Rules) h2 is still just a section label in plain flow. */
.report-section h2, .chart-box h2 { font-size: 20px; margin: 0 0 12px; }

/* Chart.js sizes its canvas from this wrapper's actual height (each chart
   below sets maintainAspectRatio: false so it fills exactly this box
   instead of computing its own, much taller, default aspect ratio). */
.chart-canvas-wrap { height: 240px; }

/* Chart and its table side by side when there's room, table dropping
   below the chart on a narrow window (flex-wrap, no media query needed). */
.chart-with-table { display: flex; flex-wrap: wrap; gap: 20px; align-items: center; }
/* A table's own default (below) is width:100% — fine for a section on its
   own, but inside a flex:0 0 auto wrapper (sized to the table's natural,
   autofit-driven column widths rather than stretched or squeezed to share
   space evenly with the chart) that 100% fights the very thing that
   wrapper is trying to do, since "100%" gives the flex-basis calculation
   nothing real to measure. auto here lets the table's actual content
   width — not an ambiguous percentage — drive how much room it takes. */
.chart-with-table table { width: auto; }

/* The filter sits on its own line right under the title, rather than
   beside it — reads more clearly as "this date range applies to
   everything below" than a same-line title+filter competing for space. */
.chart-box-header {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 16px;
}
.chart-box-header h2 { margin-bottom: 0; }
/* Groups a section's own filter with any other same-row control (e.g.
   CoCo Income's "Sync Stripe Revenue" button) so both still land on that
   one line under the title instead of each wrapping to its own row. */
.chart-box-header-controls { display: flex; align-items: center; flex-wrap: wrap; gap: 16px; }
.chart-filter-form { display: flex; align-items: center; gap: 10px; }
.chart-filter-form label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: #666;
  font-weight: 600;
}
.chart-filter-form input[type="month"] {
  padding: 4px 6px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 13px;
  font-family: inherit;
}

/* The Month/Range switch on every filtered section — two plain buttons
   styled as one segmented control, so picking a mode reads as flipping a
   switch rather than as two unrelated buttons. */
.mode-toggle {
  display: flex;
  border: 1px solid #ccc;
  border-radius: 4px;
  overflow: hidden;
}
.mode-toggle button {
  background: white;
  border: none;
  padding: 4px 10px;
  font-size: 12px;
  font-family: inherit;
  color: #666;
  cursor: pointer;
}
.mode-toggle button + button { border-left: 1px solid #ccc; }
.mode-toggle button:hover { background: #f3f4f6; }
.mode-toggle button.active { background: #197eab; color: white; }
.mode-toggle button.active:hover { background: #197eab; }

.rename-input {
  border: 1px solid transparent;
  background: transparent;
  padding: 2px 4px;
  border-radius: 4px;
  width: auto;
  font-size: inherit;
}
.rename-input:hover, .rename-input:focus {
  border-color: #ccc;
  background: white;
}
.rename-save {
  background: none;
  border: none;
  color: #ccc;
  cursor: pointer;
  font-weight: 700;
  padding: 0 4px;
}
.rename-save:hover { color: #197eab; }

.category-cell { cursor: pointer; white-space: normal; overflow: visible; }
.category-cell:hover { background: #f3f4f6; }

/* A number that opens the transactions-modal drill-down — By Month's
   Income/Expenses cells, By Category's # Transactions/Total cells. */
.drilldown-cell { cursor: pointer; }
.drilldown-cell:hover { background: #f3f4f6; }

/* The Rules page's click-to-edit Keyword cell — same click-to-edit
   affordance as .category-cell just above. */
.rule-keyword-cell { cursor: pointer; }
.rule-keyword-cell:hover { background: #f3f4f6; }

/* The Reports page's Net Worth section — same click-to-edit affordance,
   for the Account/Item name and Balance/Amount cells (account-name-cell
   covers a synced or manual account's own name; item-label-cell is the
   same idea for a custom NetWorthItem). Each is a <span> nested inside a
   shared name/balance <td> (not a <td> of its own — see net_worth_table
   in reports.html), so it needs its own inline-block padding/radius to
   still read as a distinct clickable region rather than just plain text. */
.account-name-cell, .balance-cell, .item-label-cell, .item-amount-cell {
  display: inline-block;
  cursor: pointer;
  padding: 2px 6px;
  margin: -2px -6px;
  border-radius: 4px;
}
.account-name-cell:hover, .balance-cell:hover, .item-label-cell:hover, .item-amount-cell:hover { background: #f3f4f6; }

/* Capped rather than left to flex:2/flex:1's usual "fill the container" —
   Net Worth's card got much wider once it moved into a side-by-side row
   with Yearly Totals (see .top-row), and letting this form stretch along
   with it turned "Cash on Hand" into a text box hundreds of pixels wide. */
.add-item-form { display: flex; gap: 8px; margin-top: 12px; margin-bottom: 16px; max-width: 420px; }
.add-item-form input[type="text"] { flex: 2; min-width: 0; }
.add-item-form input[type="number"] { flex: 1; min-width: 0; max-width: 110px; }
.add-item-form input {
  padding: 8px 10px;
  border: 1px solid #ccc;
  border-radius: 6px;
  font-size: 14px;
  font-family: inherit;
}
/* Wraps a long breadcrumb (e.g. "Personal Expenses > Transportation >
   Gas") onto a 2nd line instead of cutting it off mid-word — no
   click-to-expand here like the Description column, since clicking this
   cell already opens the category editor; the cell's title attribute
   (set by table-tools.js) still covers the rare case a breadcrumb is
   long enough to clip even at 2 lines. */
.category-display {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.category-edit-input {
  width: 100%;
  box-sizing: border-box;
  padding: 4px 6px;
  border: 1px solid #197eab;
  border-radius: 4px;
  font-size: inherit;
  font-family: inherit;
}

/* Appended to <body> (not the cell) so it can extend past the table's own
   overflow:hidden — see the comment in index.html for why. Also used from
   inside the split-transaction modal (z-index 2000), so this has to sit
   above that too, not just the table — 2100 keeps it on top of any modal,
   not only the 1000-level popovers (row menu, rule popover) it started
   out only needing to clear. */
.category-suggestion-box {
  position: absolute;
  z-index: 2100;
  background: white;
  border: 1px solid #ccc;
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  max-height: 240px;
  overflow-y: auto;
}
.category-suggestion-item {
  padding: 8px 12px;
  font-size: 14px;
  white-space: nowrap;
  cursor: pointer;
}
.category-suggestion-item:hover { background: #e5f4fd; }
.category-suggestion-empty { color: #888; cursor: default; }
.category-suggestion-empty:hover { background: none; }

.category-suggestion-create {
  color: #197eab;
  font-weight: 600;
  border-top: 1px solid #eee;
}

/* The suggestion box switches into this mode in place — see the comment
   above renderCreateForm() in index.html for why it reuses the same box
   rather than opening a separate one. */
.category-create-form { padding: 10px 12px; cursor: default; }
.category-create-kind-row { font-size: 13px; margin-bottom: 8px; }
.category-create-kind-row label { font-weight: normal; margin-right: 14px; }
.category-create-kind-row input { width: auto; margin-right: 4px; }
.category-create-error { color: #b84c51; font-size: 12px; min-height: 16px; margin-bottom: 4px; }
.category-create-buttons { display: flex; gap: 8px; }
.category-create-buttons .btn { padding: 6px 12px; font-size: 13px; }

.checkbox-cell { text-align: center; }

.txn-search-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }
.txn-search-input {
  width: 100%;
  max-width: 340px;
  box-sizing: border-box;
  padding: 8px 10px;
  border: 1px solid #ccc;
  border-radius: 6px;
  font-size: 14px;
  font-family: inherit;
}
.txn-search-input:focus { outline: none; border-color: #197eab; }
.txn-search-clear {
  background: none;
  border: none;
  color: #888;
  cursor: pointer;
  font-size: 13px;
  padding: 4px;
  line-height: 1;
}
.txn-search-clear:hover { color: #1a1a1a; }
.txn-search-count { font-size: 13px; color: #666; white-space: nowrap; }

.search-hidden { display: none; }

.bulk-bar {
  display: none;
  align-items: center;
  gap: 12px;
  background: #e5f4fd;
  border: 1px solid #bfdbfe;
  border-radius: 8px;
  padding: 10px 16px;
  margin-bottom: 16px;
}
.bulk-bar.visible { display: flex; }
#bulk-count { font-weight: 600; color: #197eab; white-space: nowrap; }
#bulk-category-input { max-width: 280px; }

.approval-cell { cursor: pointer; text-align: center; }
.approval-badge {
  border: none;
  border-radius: 999px;
  width: 28px;
  height: 28px;
  padding: 0;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.approval-badge.approved { background: #dceed8; color: #418032; }
.approval-badge.pending { background: #fef3c7; color: #b45309; }
.approval-badge:hover { filter: brightness(0.95); }

/* Lets the transaction table scroll on its own — sideways (e.g. after
   widening/reordering columns) and, once it's taller than the viewport has
   room for, vertically too — without the whole page (nav, bulk bar)
   scrolling along with it. A bounded height is what makes the sticky header
   below actually work: `overflow-x: auto` alone forces the browser to also
   treat the y-axis as a scroll container (a CSS spec quirk — the two axes
   can't be split, non-visible on one forces non-visible on the other), so
   without a real height the table was already an inert "scroll" box with
   nothing to scroll, which pins position:sticky to that box instead of the
   page and makes it a no-op. Giving it genuine, bounded room to scroll in
   turns that quirk into the intended behavior. */
.table-scroll {
  overflow: auto;
  max-height: calc(100vh - 260px);
}

/* Appended to <body> (not the row) so it isn't clipped by the table's own
   overflow:hidden / overflow-x:auto — same reasoning as the category
   suggestion box. */
.rule-popover {
  position: absolute;
  z-index: 1000;
  background: white;
  border: 1px solid #ccc;
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  padding: 16px;
  width: 320px;
}
.rule-popover label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  color: #666;
  margin: 12px 0 4px;
}
.rule-popover label:first-of-type { margin-top: 0; }
.rule-popover input {
  width: 100%;
  box-sizing: border-box;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 6px;
}
.rule-popover-description {
  font-size: 12px;
  color: #888;
  background: #f9f9f7;
  border-radius: 4px;
  padding: 6px 8px;
  max-height: 48px;
  overflow-y: auto;
  word-break: break-word;
}
.rule-keyword-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.rule-keyword-chip {
  border: 1px solid #ccc;
  background: #f3f4f6;
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 12px;
  cursor: pointer;
}
.rule-keyword-chip:hover { background: #e9eaee; }
.rule-popover-actions { display: flex; gap: 8px; margin-top: 16px; }

/* Same reasoning as .rule-popover above (appended to <body>, escapes the
   table's own overflow clipping) — this one is read-only: it looks up
   what a "STRIPE...TRANSFER" deposit actually was without ever touching
   the transaction row itself. */
.stripe-detail-popover {
  position: absolute;
  z-index: 1000;
  background: white;
  border: 1px solid #ccc;
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  padding: 16px;
  width: 340px;
}
.stripe-detail-summary { font-size: 12px; font-weight: 600; color: #666; margin-bottom: 8px; }
.stripe-detail-charge {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 13px;
  padding: 6px 0;
  border-bottom: 1px solid #eee;
}
.stripe-detail-charge:last-of-type { border-bottom: none; }
.stripe-detail-charge span:first-child { word-break: break-word; }
.stripe-detail-charge span:last-child { flex-shrink: 0; }
.stripe-detail-note { font-size: 11px; color: #999; margin-top: 10px; }
.stripe-detail-actions { display: flex; gap: 8px; margin-top: 16px; }

.actions-cell { text-align: center; }
.row-menu-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  padding: 4px 10px;
  border-radius: 4px;
  color: #666;
}
.row-menu-btn:hover { background: #f3f4f6; color: #1a1a1a; }

/* Appended to <body> (not the row) for the same reason as the other
   popovers on this page — it needs to escape the table's own overflow
   clipping. */
.row-menu {
  position: absolute;
  z-index: 1000;
  background: white;
  border: 1px solid #ccc;
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  padding: 4px;
  min-width: 150px;
}
.row-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 8px 12px;
  font-size: 14px;
  cursor: pointer;
  border-radius: 4px;
  white-space: nowrap;
}
.row-menu-item:hover { background: #e5f4fd; }
.row-menu-item.danger { color: #b84c51; }
.row-menu-item.danger:hover { background: #ffdfde; }

/* Alternates per duplicate GROUP (not per row) on the duplicates review
   page, so every transaction that shares a date/amount/account visually
   clusters together. */
.dup-group-even { background: #fafafa; }
.dup-group-odd { background: #ffffff; }

.dup-badge {
  display: inline-block;
  border-radius: 999px;
  padding: 2px 8px;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}
.dup-badge-synced { background: #e5f4fd; color: #197eab; }
.dup-badge-unsynced { background: #f3f4f6; color: #666; }

/* Only ever visible when "Show transfers" is on — muted, since these are
   excluded from reports and not really part of your income/expense picture. */
.transfer-row { color: #999; background: #fafafa; }
.transfer-row .amount-income, .transfer-row .amount-expense { color: inherit; font-weight: 400; }

/* Overrides the generic single-line ellipsis truncation (see the th, td
   rule above) — this column instead shows up to 2 wrapped lines by
   default, click to see the rest. overflow:visible on the cell itself
   hands clipping duty to .description-text so the row can still grow
   taller to fit the expanded state. */
.description-cell {
  cursor: pointer;
  white-space: normal;
  overflow: visible;
}
.description-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.description-cell.expanded .description-text {
  -webkit-line-clamp: unset;
  display: block;
  overflow: visible;
}
