/* SPACING: reach for a token — `--card-pad`, `--card-gap`, `--sp-1`…`--sp-6` —
   not a raw rem, and not the value in the rule above. Cards on the padding
   contract are enforced by `test/layout-tokens.test.js`; the contract and the
   list of what is on it are in `docs/visual-language.md` → Tokens. Raw values
   still in this file are legacy from a 2px grid, not a precedent to follow. */
/* Shared chrome: modals, toast, context menu, topbar. */
/* ══ Shared chrome: modals, toast, context menu, topbar ════════════════ */
/* Anchored near the top (not vertically centered) so a long item list has the
   whole page height below it; the backdrop scrolls if the modal outgrows it. */
.modal-backdrop{
  position:fixed;
  inset:0;
  background:var(--scrim);
  display:none;
  align-items:flex-start;
  justify-content:center;
  z-index:300;
  overflow-y:auto;
  padding:2.5rem 0;
  backdrop-filter:blur(2px);
}
.modal-backdrop.open{display:flex;}
/* ── modals: match the inspiration's centred white sheet ── */
.modal{
  background:var(--surface);
  padding:1.375rem;
  width:min(33.75rem,92vw);
  animation:modalIn .22s cubic-bezier(.22,1,.36,1);
  border-radius:var(--radius-lg);
  border:1px solid var(--border);
  box-shadow:0 24px 60px -20px rgba(0,0,0,.55);
}
@keyframes modalIn{
  from{opacity:0;transform:translateY(12px) scale(.98);}
  to{opacity:1;transform:translateY(0) scale(1);}
}
/* Nudges the modal on an items-total-exceeds-amount mismatch at save time. */
@keyframes modalShake{
  10%,90%{transform:translateX(-1px);}
  20%,80%{transform:translateX(2px);}
  30%,50%,70%{transform:translateX(-4px);}
  40%,60%{transform:translateX(4px);}
}
.modal.shake{animation:modalShake .4s;}
#qa-items-remainder.over{color:var(--neg);font-weight:600;}
.modal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--sp-4);}
.modal-head h2{font-size:var(--fs-body);}
.modal-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-3);}
.modal .field{display:flex;flex-direction:column;gap:0.3125rem;}
.modal .field.full{grid-column:1/-1;}
.modal-grid .form-recur{
  grid-column:1/-1;
  margin:2px 0 0;margin-inline-start:2px;
  text-transform:none;
  letter-spacing:normal;
  font-size:var(--fs-body);
  font-weight:400;
  color:var(--text);
}
.modal label,.modal .fld-label{
  font-size:var(--fs-label);
  color:var(--muted);
  font-weight:500;
  text-transform:none;
}
.modal .field.form-recur-narrow{max-width:14rem;}
.modal .hint.tight{margin-top:var(--sp-1);}
.modal input:not([type=checkbox]),.modal select{
  background-color:var(--input-bg);
  border:1px solid var(--border);
  color:var(--text);
  border-radius:var(--radius-sm);
  padding:0.5rem var(--sp-3);
  min-height:2.375rem;
  font-size:var(--fs-body);
  outline:none;
  width:100%;
  transition:border-color .15s;
}
/* Heebo (description face) runs optically larger than Quicksand at the same size,
   so the description box dropped to the label size to sit level with its neighbours. */
.modal input#qa-desc,.modal input#qi-desc{font-size:var(--fs-label);}
.modal select{padding-inline-end:2.625rem;}
/* Focus glow matches the accent border it accompanies (it was still the violet
   from the pre-Steel palette, so a blue-bordered field glowed purple). */
.modal input:not([type=checkbox]):focus,.modal select:focus,.modal .date-picker-trigger:focus{box-shadow:0 0 0 3px var(--tint-amber);border-color:var(--accent);}
.modal .date-picker-panel{z-index:30;}
.modal h2,.modal h3{font-weight:600;letter-spacing:-.01em;}
.modal-actions{display:flex;justify-content:flex-end;gap:0.625rem;margin-top:1.125rem;}
.toast{
  position:fixed;
  bottom:26px;
  left:50%;
  transform:translate(-50%,10px);
  background:var(--surface3);
  border:1px solid var(--border);
  color:var(--text);
  padding:0.625rem 1.25rem;
  border-radius:var(--radius);
  box-shadow:var(--elev-hover);
  font-size:var(--fs-body);
  opacity:0;
  pointer-events:none;
  transition:opacity .25s,transform .25s;
  z-index:400;
}
.toast.show{opacity:1;transform:translate(-50%,0);}
/* Undo variant: needs actual clicks (base .toast is pointer-events:none) and
   room for the button, so it gets its own layout instead of plain centered text. */
.toast.undo{
  pointer-events:auto;
  display:flex;
  align-items:center;
  gap:0.875rem;
  padding-block:0.5625rem;padding-inline:1.25rem 0.625rem;
}
/* Fixed to the viewport (not a scrolling/overflow:hidden ancestor) so it can
   never get clipped by a card's edge; JS flips it above the button and clamps
   it horizontally when there isn't room below/to the right. */
.floating-popover{
  position:fixed;
  z-index:500;
  background:var(--surface3);
  border:1px solid var(--border);
  border-radius:var(--radius);
  padding:0.375rem 0;
  min-width:10.625rem;
  max-height:13.75rem;
  overflow-y:auto;
  box-shadow:var(--elev-hover);
  display:none;
}
.sec-info{
  flex:0 0 auto;
  width:1.5rem;
  height:1.5rem;
  padding:0;
  border:1px solid var(--border);
  border-radius:50%;
  background:transparent;
  color:var(--muted);
  display:inline-flex;
  align-items:center;
  justify-content:center;
  cursor:pointer;
}
.sec-info svg{width:0.875rem;height:0.875rem;}
.sec-info:hover,.sec-info[aria-expanded="true"]{
  color:var(--text);
  border-color:var(--border-hover);
  background:var(--surface2);
}
.floating-popover.prose{
  min-width:16rem;
  max-width:22rem;
  max-height:min(24rem,70vh);
  padding:var(--sp-4);
  font-size:var(--fs-body);
  color:var(--muted);
  line-height:1.55;
}
.floating-popover.prose p{margin:0 0 var(--sp-3);}
.floating-popover.prose p:last-child{margin:0;}
/* topbar add button matches the crimson primary */
/* Admin category lists: the base rule is a hard 1fr 1fr, which inside the
   three-up admin grid leaves ~100px per item — long names ("Dining & Takeout")
   wrapped to two lines and spilled past the pill. Let the columns collapse when
   there isn't room, and truncate rather than wrap so every row is one line high. */
/* 190px left every chip ~87px for its label, so "Dining & Takeout",
   "Transportation" and "Personal Care" all rendered as ellipses. auto-fit spends
   extra width on MORE columns, never on wider ones, so the floor is what decides
   whether a name fits: 226px is the narrowest chip that shows the longest
   category in full. Below that the list is one readable column; a genuinely wide
   screen gets two. */
#panel-admin .cat-list{grid-template-columns:repeat(auto-fit,minmax(14.125rem,1fr));}
#panel-admin .cat-item{min-width:0;}
/* keep the ⋮ affordance from being squeezed out by a long name */
#panel-admin .cat-item .ctx-hint{flex:0 0 auto;}
/* an editing row needs the full width of the list */
#panel-admin .cat-item.editing{grid-column:1/-1;white-space:normal;}
#panel-admin .cat-item.editing .cat-name{white-space:normal;overflow:visible;}
/* Page-ground chrome: sidebar Guide / backup and topbar Export / Add share one
   card — surface, hairline, 12px radius, centred label. Rank is fill only. */
.side-guide,.sidebar #backup-badge,.topbar-actions .btn{
  box-sizing:border-box;
  min-height:2.25rem;
  padding:0.4rem 0.55rem;
  display:flex;
  align-items:center;
  justify-content:center;
  gap:0.55rem;
  font-size:var(--fs-body);
  font-weight:500;
  border:1px solid var(--border);
  border-radius:var(--radius);
  background:var(--surface);
  color:var(--text);
  box-shadow:var(--elev);
  line-height:1.25;
  text-align:center;
  cursor:pointer;
}
.side-guide:hover,.sidebar #backup-badge:hover,.topbar-actions .btn.secondary:hover,#topbar-pdf:hover{
  background:var(--surface2);
  transition:background var(--hover-dur) var(--hover-ease);
}
.topbar-actions{display:flex;align-items:center;gap:var(--sp-3);}
.topbar-actions .btn svg,.side-guide svg{width:1.05rem;height:1.05rem;flex:0 0 auto;}
.topbar-actions .btn.secondary svg{color:var(--muted);}
.topbar-actions .btn.primary{
  background:var(--accent-bright);
  color:var(--accent-bright-ink);
  font-weight:600;
}
.topbar-actions .btn.primary:hover{background:var(--accent-bright);filter:brightness(1.10);}
.topbar-actions .btn[disabled]{opacity:.6;cursor:default;}
/* Guide and the backup badge do not shrink on press. The shared card rule
   already paints this button; .btn:active must not pull it back into a control. */
#topbar-pdf:active{transform:none;}
/* Row actions cell stays a real table cell. As display:flex it dropped out of the
   table layout, so its border and row tint drew as a short stray block at the row's
   right end instead of lining up with the other cells. */
td.row-actions{display:table-cell;width:1%;text-align:end;vertical-align:middle;}
td.row-actions > *,tr.editing-row td.row-actions > .btn{display:inline-flex;vertical-align:middle;}
td.row-actions > * + *{margin-inline-start:0.375rem;}
/* ── Row right-click menu (Expenses/Income + their Recurring tables, and the
   Admin category/source lists). The whole component, base and all: nothing
   else restates it. Delete gets no fill until hovered — painted solid it put
   --danger text on a --danger background and the label vanished. */
.ctx-menu{
  display:none;
  position:fixed;
  z-index:500;
  min-width:10.5rem;
  background:var(--surface2);
  border:1px solid var(--border);
  border-radius:var(--radius);
  padding:0.375rem;
  overflow:hidden;
  box-shadow:0 14px 34px -10px rgba(0,0,0,0.55),0 0 0 1px rgba(255,255,255,0.02);
}
.ctx-menu.show{display:block;animation:ctxIn .12s ease-out;}
@keyframes ctxIn{
  from{opacity:0;transform:translateY(-3px);}
  to{opacity:1;transform:none;}
}
.ctx-item{
  display:flex;
  align-items:center;
  gap:0.625rem;
  width:100%;
  padding:var(--sp-2) var(--sp-3);
  border:none;
  background:none;
  color:var(--text);
  font-size:var(--fs-body);
  font-weight:500;
  border-radius:var(--radius-sm);
  cursor:pointer;
  text-align:start;
}
.ctx-item svg{width:0.875rem;height:0.875rem;flex:0 0 auto;color:var(--muted);}
.ctx-item:hover svg{color:inherit;}
.ctx-item.danger{background:none;}
.ctx-item.danger,.ctx-item.danger svg{color:var(--neg);}
.ctx-item.danger:hover{background:var(--tint-red);color:var(--neg);}
.ctx-sep{height:1px;background:var(--border);margin:0.3125rem var(--sp-2);opacity:.7;}
