/* Shared components: metric cards, panels, toggles, empty state, toast */

.cards-row{
  display:grid;
  grid-template-columns:repeat(5, 1fr);
  gap:14px;
  margin-bottom:18px;
}
.metric-card{
  background:var(--card);
  border:1px solid var(--line);
  border-radius:var(--radius);
  padding:18px 18px 16px;
  box-shadow:var(--shadow-card);
  display:flex;
  flex-direction:column;
  gap:10px;
  min-height:118px;
  opacity:0;
  animation:cardIn .5s ease forwards;
}
.metric-card:nth-child(1){animation-delay:.02s;}
.metric-card:nth-child(2){animation-delay:.07s;}
.metric-card:nth-child(3){animation-delay:.12s;}
.metric-card:nth-child(4){animation-delay:.17s;}
.metric-card:nth-child(5){animation-delay:.22s;}
@keyframes cardIn{from{opacity:0; transform:translateY(6px);} to{opacity:1; transform:translateY(0);}}

.metric-top{
  display:flex;
  align-items:center;
  justify-content:space-between;
}
.metric-label{
  font-size:12.5px;
  font-weight:600;
  color:var(--text-dim);
}
.metric-value{
  font-family:var(--font-mono);
  font-size:22px;
  font-weight:700;
  letter-spacing:-0.01em;
  color:var(--text);
}
.metric-foot{
  font-size:11.5px;
  color:var(--text-faint);
}
.metric-card.dark{
  background:var(--invert);
  border-color:var(--invert);
}
.metric-card.dark .metric-label{color:var(--invert-dim);}
.metric-card.dark .metric-value{color:var(--invert-text);}
.metric-card.dark .metric-foot{color:var(--invert-faint);}
.metric-card.balance{
  background:linear-gradient(160deg, #2866b8, #050417);
  border-color:var(--halo);
}
.metric-card.balance .metric-label{color:#bcd4f4;}
.metric-card.balance .metric-value{color:#fbfbfe;}
.metric-card.balance .metric-foot{color:#93b3dd;}
.metric-card.accent{
  background:linear-gradient(160deg, #21624f, #143b30);
  border-color:#143b30;
}
.metric-card.accent .metric-label{color:#bfe4d6;}
.metric-card.accent .metric-value{color:#fbfffd; font-family:var(--font-display); font-size:19px;}
.metric-card.accent .metric-foot{color:#9cccb8;}
.metric-card.accent .metric-amount{font-family:var(--font-mono); font-size:15px; color:#e6fbf3; font-weight:600;}
/* Over budget: the verdict and the amount it is over by, in yellow. --gold is
   tuned for light backgrounds and goes muddy on this green; this is 4.98:1 on
   the gradient's lightest end and 8.58:1 on its darkest. */
.metric-card.accent.is-over .metric-value,
.metric-card.accent.is-over .metric-amount{color:#ffd166;}
.metric-icon{
  width:26px;height:26px;
  border-radius:7px;
  display:flex;align-items:center;justify-content:center;
  background:var(--line-soft);
  flex-shrink:0;
}
.metric-card.dark .metric-icon, .metric-card.balance .metric-icon, .metric-card.accent .metric-icon{background:rgba(255,255,255,0.1);}
.val-pos{color:var(--teal) !important;}
.val-neg{color:var(--rust) !important;}

.mid-row{
  display:grid;
  grid-template-columns:1.62fr 1fr;
  gap:14px;
  margin-bottom:14px;
  align-items:stretch;
}
.panel{
  background:var(--card);
  border:1px solid var(--line);
  border-radius:var(--radius);
  box-shadow:var(--shadow-card);
  padding:20px 22px 18px;
  display:flex;
  flex-direction:column;
}
.panel-head{
  display:flex;
  justify-content:space-between;
  align-items:flex-start;
  margin-bottom:6px;
}
.panel-title{
  font-family:var(--font-display);
  font-size:15.5px;
  font-weight:700;
  margin:0;
}
.panel-sub{
  font-size:11.5px;
  color:var(--text-faint);
  margin:2px 0 0;
}
/* One row shared by the legend and the pager. Its height is held steady so the
   chart below does not shift when the legend hides on the savings view. */
.chart-toolbar{
  display:flex;
  align-items:center;
  gap:14px;
  min-height:22px;
}
.legend{
  display:flex;
  align-items:center;
  gap:14px;
  font-size:11.5px;
  color:var(--text-dim);
}
.legend-item{display:flex; align-items:center; gap:6px;}
.legend-dot{width:8px;height:8px;border-radius:2px;}

/* Pager. Shared by both chart views and the transactions table, and hidden
   outright wherever everything already fits on one page.
   Pinned right with margin-left rather than the row's justify-content: the
   chart legend is hidden on the savings view, and space-between would drop the
   lone pager back to the left. */
.pager{display:flex; gap:4px; margin-left:auto;}
.pager-btn{
  width:22px; height:22px;
  padding:0;
  display:flex; align-items:center; justify-content:center;
  border:1px solid var(--line);
  border-radius:6px;
  background:var(--card);
  color:var(--text-dim);
  font-size:14px; line-height:1;
  cursor:pointer;
  transition:background .15s, color .15s, border-color .15s;
}
.pager-btn:hover:not(:disabled){background:var(--paper); color:var(--text); border-color:var(--line-input);}
.pager-btn:disabled{opacity:0.35; cursor:default;}

/* discreet cash-flow / savings-goal toggle */
.view-toggle{
  display:flex;
  background:var(--line-soft);
  border-radius:8px;
  padding:2px;
  gap:2px;
  flex-shrink:0;
}
.toggle-btn{
  border:none;
  background:transparent;
  padding:6px 11px;
  font-size:11.5px;
  font-weight:600;
  color:var(--text-dim);
  border-radius:6px;
  cursor:pointer;
  white-space:nowrap;
  transition:background .15s ease, color .15s ease;
}
.toggle-btn:hover:not(.active){color:var(--text);}
.toggle-btn.active{
  background:var(--card);
  color:var(--text);
  box-shadow:0 1px 2px rgba(17,18,28,0.1);
}

.empty-state{
  padding:30px 10px;
  text-align:center;
  color:var(--text-faint);
  font-size:13px;
}

/* ---- Buttons ---- */

.btn{
  border:1px solid var(--line);
  background:var(--card);
  padding:9px 15px;
  border-radius:9px;
  font-size:13px;
  font-weight:600;
  color:var(--text);
  cursor:pointer;
  display:flex;
  align-items:center;
  gap:7px;
  transition:transform .1s ease, box-shadow .15s ease;
}
.btn:hover:not(:disabled){box-shadow:var(--shadow-card);}
.btn:active:not(:disabled){transform:scale(0.98);}
/* Buttons are disabled while something is in flight — an import, a reset, a
   transaction being saved, a confirmation being resent — and at the account
   limit, and until this rule none of them looked any different from a button
   that works. Same treatment as .pager-btn, which had it already.

   Every :hover in this family carries :not(:disabled), the three variants below
   included. CSS :hover matches a disabled control — the browser suppresses the
   click, not the style — so a variant rule without the guard repaints a dead
   button the moment the cursor arrives, which is precisely when the user is
   asking whether it is alive. Dimming it and then lighting it up answers twice,
   and the second answer is the wrong one. */
.btn:disabled{opacity:0.35; cursor:default;}
/* The strongest thing on the page, so it inverts with the theme: near-black
   on a light page, near-white on a dark one. */
.btn-primary{
  background:var(--btn-bg);
  color:var(--btn-fg);
  border-color:var(--btn-bg);
}
.btn-primary:hover:not(:disabled){background:var(--btn-bg-hover); border-color:var(--btn-bg-hover);}
.btn-ghost{
  background:transparent;
  border-color:var(--line);
  color:var(--text-dim);
  font-size:12.5px;
  padding:7px 13px;
}
.btn-ghost:hover:not(:disabled){color:var(--text); background:var(--card);}
.btn-danger{
  background:var(--rust-soft);
  border-color:var(--rust-soft);
  color:var(--rust);
}
.btn-danger:hover:not(:disabled){background:var(--rust); color:var(--on-accent); border-color:var(--rust);}

#toast{
  position:fixed;
  bottom:24px;
  left:50%;
  transform:translateX(-50%) translateY(20px);
  background:var(--invert);
  color:var(--invert-text);
  padding:12px 18px;
  border-radius:10px;
  font-size:13px;
  box-shadow:0 10px 30px rgba(0,0,0,0.25);
  opacity:0;
  pointer-events:none;
  transition:opacity .25s ease, transform .25s ease;
  z-index:100;
  max-width:420px;
}
#toast.show{opacity:1; transform:translateX(-50%) translateY(0);}
#toast.error{background:var(--rust);}

/* Second line of an empty state: the suggested next step, under the headline.
   .empty-state is already centred, so this only needs to become its own line. */
.empty-hint{
  display:block;
  margin-top:5px;
  color:var(--text-faint);
  opacity:0.85;
}

/* -------------------------------------------------------------- busy overlay --
 * The notice shown while something the user cannot cancel is in flight. Two
 * things use it: reading a spreadsheet, and signing out.
 *
 * An overlay rather than a button state, because the work is not cancellable and
 * what is underneath is about to be replaced — dimming it says so. It sits above
 * the toast, though the two never overlap: the toast reports the result once this
 * has gone. It sits above the sign-in gate too, which is what lets sign-out lift
 * the scrim onto the gate rather than back onto the dashboard.
 *
 * Everything here is a token, so it follows system, light and dark without a
 * second rule. --scrim is the one colour added for it, and is heavier in dark,
 * where a light scrim lifts the page instead of pushing it back.
 */
.busy-overlay{
  position:fixed;
  inset:0;
  z-index:120;                    /* over the toast's 100 and the gate's 60 */
  display:grid;
  place-items:center;
  padding:20px;
  background:var(--scrim);
  -webkit-backdrop-filter:blur(2px);
  backdrop-filter:blur(2px);
}

/* display:grid above would otherwise beat the browser's own [hidden] rule. */
.busy-overlay[hidden]{display:none;}

.busy-panel{
  background:var(--card);
  border:1px solid var(--line);
  border-radius:12px;
  box-shadow:var(--shadow-card);
  padding:16px 20px;
  max-width:100%;
}

.busy-line{
  display:flex;
  align-items:center;
  gap:10px;
  margin:0;
  font-size:14px;
  font-weight:600;
  color:var(--text);
}

/* Indented past the spinner, so the two lines share a left edge. */
.busy-note{
  margin:5px 0 0 23px;
  font-size:12px;
  color:var(--text-faint);
}

.busy-spin{
  width:13px;
  height:13px;
  flex-shrink:0;
  border-radius:50%;
  border:2px solid var(--teal);
  border-top-color:transparent;
  animation:busy-spin 0.62s linear infinite;
}

@keyframes busy-spin{ to{ transform:rotate(360deg); } }

/* The global reduced-motion rule only shortens durations, which would leave this
   spinning too fast to read. Stop it outright and close the ring instead. */
@media (prefers-reduced-motion: reduce){
  .busy-spin{
    animation:none;
    border-top-color:var(--teal);
    opacity:0.55;
  }
}
