:root {
  color-scheme: light;

  /* Cool paper ground with a deep teal accent, replacing the old warm
     cream/terracotta scheme entirely.

     The accent has to stay unmistakably distinct from everything else on
     screen: two strong brand colours (MYOB purple, Xero blue) plus the
     green/amber/red status palette. A deep wine/plum sits clear of all of
     them - warmer than the brands, darker and less red than the error
     colour it might otherwise be confused with.

     Body text is a soft near-black rather than the accent; a coloured
     accent earns its attention by being rare. */
  --bg: #EDEFF2;
  --bg-elevated: #FFFFFF;
  --text: #12161C;
  --text-muted: #59616D;
  --border: #DCE0E6;
  --shadow: 0 1px 2px rgba(18, 22, 28, 0.04), 0 12px 28px -12px rgba(18, 22, 28, 0.18);

  --accent: #7A2E48;
  --accent-hover: #61233A;
  --accent-contrast: #FFFFFF;
  --accent-ring: rgba(122, 46, 72, 0.28);

  /* Status colours stay conventional (green/amber/red) - these are read at
     a glance during long runs and are not the place to be clever. */
  --success: #2F7A3E;
  --success-bg: #E0F0DF;
  --warn: #8A5B0B;
  --warn-bg: #FAEBCE;
  --neutral-bg: #E5E9EE;

  --error: #B23A32;
  --error-bg: #FADFDC;

  /* Brand accents for the MYOB/Xero cards specifically - approximations of
     each product's real brand color, used sparingly (only the one primary
     CTA per card, plus focus rings) so each card reads as "belonging" to
     that product without turning every control into a loud colored block. */
  --myob-accent: #4E22CC;
  --myob-accent-hover: #3F1BA6;
  --myob-accent-ring: rgba(78, 34, 204, 0.22);

  --xero-accent: #1E8FD6;
  --xero-accent-hover: #1873AD;
  --xero-accent-ring: rgba(30, 143, 214, 0.22);

  /* System stacks, not a web font - no CDN access from this app, so the
     polish comes from disciplined weight/spacing/scale rather than a
     downloaded typeface. Tokenized so both declarations below (and the
     terminal panel) stay in lockstep instead of silently drifting apart. */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: "SF Mono", "Cascadia Code", Consolas, monospace;
}

* { box-sizing: border-box; }

html {
  overflow-x: hidden;
}

body {
  margin: 0;
  overflow-x: hidden;
  font-family: var(--font-sans);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  /* The faintest possible wash of each brand's colour in opposite corners -
     MYOB where the source sidebar sits, Xero out to the right where the
     destination lives. Barely perceptible on its own; it just stops the
     ground reading as a flat grey sheet. */
  background-color: var(--bg);
  background-image:
    radial-gradient(circle at 10% -8%, rgba(78, 34, 204, 0.05), transparent 40%),
    radial-gradient(circle at 92% 12%, rgba(30, 143, 214, 0.05), transparent 38%);
  background-attachment: fixed;
}

/* Unified on the same clean system sans-serif as the body instead of a
   fallback web-safe serif (Georgia) - modern professional dashboards read
   as considered typography through weight/spacing/scale, not a second
   typeface bolted on for "importance." No web fonts here (no CDN access
   from this app), so the polish comes from disciplined use of the actual
   system UI fonts rather than reaching for a decorative face. */
h1, h2, h3 {
  font-family: var(--font-sans);
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 0;
}

/* --- Login page --- */

.login-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

/* 14px, not the old 20px - the softer pill-like radius read as consumer
   app; this is a precision tool for finance work and a crisper corner
   suits it, while still being clearly rounded rather than hard-edged. */
.card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow);
  padding: 32px;
  margin-bottom: 24px;
  min-width: 0;
}


.login-card {
  width: 360px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.login-logo {
  height: 44px;
  width: auto;
  align-self: center;
}

.login-card h1 {
  font-size: 1.4rem;
  text-align: center;
}

.subtitle {
  margin: -8px 0 0;
  color: var(--text-muted);
  font-size: 0.9rem;
}

label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 0.85rem;
  color: var(--text-muted);
}

input {
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
  font-family: inherit;
  width: 100%;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}

.card--myob input:focus {
  border-color: var(--myob-accent);
  box-shadow: 0 0 0 3px var(--myob-accent-ring);
}

.card--xero input:focus {
  border-color: var(--xero-accent);
  box-shadow: 0 0 0 3px var(--xero-accent-ring);
}

/* --- Buttons ---
   One explicit primary button per card (the actual CTA) styled with the
   brand color; everything else - secondary actions, the dropdown trigger -
   stays neutral. Scoping by a dedicated .button--primary class (rather than
   "any button that isn't secondary") keeps this from ever leaking onto
   unrelated controls like the custom dropdown, which is a <button> too. */

button, .button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px 20px;
  border-radius: 9px;
  border: none;
  background: var(--neutral-bg);
  color: var(--text);
  font-size: 0.9rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  text-decoration: none;
  transition: background 0.15s ease, transform 0.05s ease;
}

button:active, .button:active {
  transform: scale(0.98);
}

.button--primary {
  background: var(--accent);
  color: var(--accent-contrast);
}

.button--primary:hover {
  background: var(--accent-hover);
}

.button--secondary {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--border);
}

.button--secondary:hover {
  background: var(--neutral-bg);
}

.button--primary:hover, .button--secondary:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 10px rgba(18, 22, 28, 0.1);
}

/* Same specificity as the :hover rule above but declared after it, so a
   press-down still visibly registers even while the cursor is hovering
   (the normal case for an actual click) instead of the lift transform
   winning outright. */
.button--primary:active, .button--secondary:active {
  transform: scale(0.98);
  box-shadow: none;
}

.button--block {
  width: 100%;
}

.error {
  color: var(--error);
  font-size: 0.85rem;
  min-height: 1.1em;
  margin: 0;
}

/* --- Dashboard --- */

/* Sidebar (brand + MYOB/Xero connections) + main content (run migration +
   job panels), replacing the old single centred column. */
.app-shell {
  display: flex;
  align-items: stretch;
  min-height: 100vh;
}

/* position: fixed, NOT sticky. Sticky silently cannot work here: html and
   body both set overflow-x: hidden (to kill horizontal scroll), and any
   overflow value other than visible turns that ancestor into a scroll
   container, which disables position: sticky for everything inside it.
   Fixed is immune to that, so the sidebar is genuinely locked to the
   viewport and never scrolls away with the page.

   Being fixed takes it out of the document flow, so .main-content carries
   a matching margin-left below to sit beside it rather than underneath.

   Deliberately no overflow/max-height here - the company-file and
   organisation dropdowns are position: absolute inside these cards, and an
   overflow value would clip an open menu. */
.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  width: 300px;
  height: 100vh;
  background: var(--bg-elevated);
  border-right: 1px solid var(--border);
  padding: 40px 24px;
  display: flex;
  flex-direction: column;
  z-index: 20;
}

/* The MYOB/Xero cards' own padding, button sizing, and dropdown spacing
   were tuned for a much wider two-column layout - too roomy for a 300px
   sidebar column, which is what made Connect/Disconnect wrap onto their
   own lines instead of sitting together. Scoped overrides only, the base
   .card/.button/.picker rules (used elsewhere, e.g. job panels) are
   untouched. */
.sidebar .card {
  padding: 20px;
  border-radius: 16px;
}

.sidebar .connect-row {
  flex-wrap: nowrap;
  gap: 8px;
}

.sidebar .connect-row .button {
  flex: 1 1 0;
  min-width: 0;
  padding: 9px 10px;
  font-size: 0.85rem;
}

.sidebar .picker {
  margin-top: 12px;
  gap: 8px;
}

.sidebar .picker label {
  font-size: 0.85rem;
}

.sidebar .status {
  margin: 2px 0 10px;
}

/* Labels what this column actually is. Letter-spaced uppercase at a small
   size is doing real work here - it reads as a section marker rather than
   competing with the card headings below it. */
.sidebar-eyebrow {
  margin: 0 0 12px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* margin-top: auto in a flex column pushes this to consume all remaining
   space and sit flush at the bottom of .sidebar's now-fixed (100vh) box,
   regardless of how tall the cards above it are. */
.sidebar-footer {
  margin-top: auto;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.sidebar-user {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.sidebar-user__avatar {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 0.8rem;
  font-weight: 700;
  display: grid;
  place-items: center;
}

.sidebar-user__name {
  font-size: 0.85rem;
  color: var(--text);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* margin-left matches the fixed sidebar's width - it's out of flow, so
   without this the content would sit underneath it. */
.main-content {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 900px;
  margin-left: 300px;
  padding: 40px 40px 80px;
}

/* Below this width the sidebar returns to normal document flow and stacks
   above the content - a fixed panel eating a third of a small screen, with
   the page scrolling behind it, is worse than simply scrolling past it. */
@media (max-width: 900px) {
  .app-shell { flex-direction: column; }
  .sidebar {
    position: static;
    width: auto;
    height: auto;
    border-right: none;
    border-bottom: 1px solid var(--border);
    padding: 24px 24px 32px;
  }
  .main-content {
    max-width: none;
    margin-left: 0;
    padding: 32px 24px 60px;
  }
}

/* Once more than one migration is running/finished, let panels sit side by
   side on wide screens instead of one long vertical column - falls back to
   a single column below ~1080px (a panel's own content - transfer lane,
   terminal, failures list - needs real room to not feel cramped). */
/* Stacked one above the other, not side by side - side-by-side saved
   width but made two migrations harder to follow at once, especially with
   the terminal/failures sections open on both. */
#job-panels {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

#job-panels .job-panel {
  margin-bottom: 0;
}

.topbar {
  /* Only ever used inside the (narrow, ~300px) sidebar now. Brand shrinks
     to make room instead of pushing "Log out" onto the row below - and
     align-items: flex-start keeps "Log out" pinned top-right even once
     the heading wraps onto two or three lines. */
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  padding-bottom: 20px;
  margin-bottom: 24px;
  border-bottom: 1px solid var(--border);
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  flex: 1 1 auto;
}

.brand-logo {
  height: 24px;
  width: auto;
  flex-shrink: 0;
}

.topbar h1 {
  font-size: 0.92rem;
  line-height: 1.3;
  min-width: 0;
}

.link-muted {
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.8rem;
  flex-shrink: 0;
  white-space: nowrap;
  padding-top: 2px;
}

.link-muted:hover {
  color: var(--text);
}

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 4px;
}

.card-title {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  /* Forces both cards' header rows to the same height regardless of which
     logo is taller (Xero's badge vs MYOB's wordmark are different
     intrinsic sizes) - otherwise everything below (status text, buttons,
     dropdown) sits at a different vertical position between the two
     side-by-side cards. */
  min-height: 38px;
}

.card-header h2 {
  font-size: 1.15rem;
}

/* Real brand logos, not hand-drawn approximations. MYOB's is a wordmark
   (the logo already says "MYOB", so no separate heading next to it), Xero's
   is a round icon badge (keeps the "Xero" text heading alongside it, since
   the wordmark inside that badge is too small to read at this size). */
.card-logo { display: block; width: auto; }
/* Same 28px height as the MYOB crop below - they used to be 30px/24px,
   a mismatch that threw the two cards' header rows out of alignment. */
/* Bigger than MYOB's crop height (28px) on purpose - a small circle badge
   next to a wide wordmark still reads as visually "smaller" even at equal
   height, since it occupies far less width/area. This closes that gap. */
.card-logo--xero { height: 38px; border-radius: 50%; }

/* myob-logo.png has huge invisible margins baked in - the real wordmark is
   only a 170x70px region inside a 245x227px canvas (measured directly from
   the file), which made it render almost illegibly small at any sane
   height. This crops it visually: the image is scaled up and shifted so
   only that measured region shows through a fixed-size window, instead of
   shrinking the whole mostly-empty canvas down to fit. */
.card-logo-crop {
  position: relative;
  display: inline-block;
  overflow: hidden;
}
.card-logo-crop--myob {
  width: 68px;
  height: 28px;
}
.card-logo-crop--myob img {
  position: absolute;
  left: -14px;
  top: -36px;
  width: 98px;
  height: 91px;
}

.status {
  color: var(--text-muted);
  font-size: 0.95rem;
  margin: 6px 0 16px;
}

.status strong {
  color: var(--text);
}

/* --- Badges --- */

.badge {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.badge--on {
  background: var(--success-bg);
  color: var(--success);
}

.badge--mid {
  background: var(--warn-bg);
  color: var(--warn);
}

.badge--off {
  background: var(--neutral-bg);
  color: var(--text-muted);
}

.connect-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* --- Pickers & custom dropdown --- */

.picker {
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Custom dropdown replaces the native <select> - browsers position a native
   select's option list wherever they decide there's room (often flipping
   upward), which we can't control or style. This is a plain div/button
   listbox instead, so the menu always opens directly below its trigger
   and matches the rest of the page's styling. */
.custom-select {
  position: relative;
}

.custom-select-trigger {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 7px 10px;
  font-size: 0.88rem;
  font-family: inherit;
  font-weight: 400;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.custom-select-trigger:hover {
  background: var(--neutral-bg);
}

.custom-select-trigger::after {
  content: "";
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: rotate(45deg) translateY(-2px);
}

.custom-select.open .custom-select-trigger {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}

.card--myob .custom-select.open .custom-select-trigger {
  border-color: var(--myob-accent);
  box-shadow: 0 0 0 3px var(--myob-accent-ring);
}

.card--xero .custom-select.open .custom-select-trigger {
  border-color: var(--xero-accent);
  box-shadow: 0 0 0 3px var(--xero-accent-ring);
}

/* Fixed, with top/left/width/max-height all set by positionDropdownMenu()
   in app.js - see the comment there. In short: absolute positioning under
   the trigger sent this off the bottom of the screen for the lower card in
   the fixed sidebar, so placement is measured against the viewport instead
   and flips above the trigger when there isn't room below.
   z-index must beat the sidebar's own (20). */
.custom-select-menu {
  position: fixed;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow);
  overflow-y: auto;
  overscroll-behavior: contain;
  z-index: 60;
}

.custom-select-menu[hidden] {
  display: none;
}

/* Pinned to the top of the scrollable menu (not the parent's padding, which
   moved onto .custom-select-options below) so it stays visible the whole
   time you're scrolling a long list, instead of scrolling away with it. */
.custom-select-search {
  position: sticky;
  top: 0;
  z-index: 1;
  width: 100%;
  padding: 9px 12px;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 12px 12px 0 0;
  background: var(--bg-elevated);
  color: var(--text);
  font-size: 0.85rem;
  font-family: inherit;
}

/* Stays on the elevated surface when focused - filling it with the grey
   neutral read as a disabled field rather than the active one. */
.custom-select-search:focus {
  outline: none;
  box-shadow: none;
  border-bottom-color: var(--accent);
  background: var(--bg-elevated);
}

.custom-select-options {
  padding: 6px;
}

.custom-select-option {
  padding: 7px 10px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 0.85rem;
  line-height: 1.35;
  /* Long company-file names shouldn't silently truncate - you need to be
     able to tell two similarly-named clients apart before clicking. */
  overflow-wrap: anywhere;
}

.custom-select-option:hover {
  background: var(--neutral-bg);
}

/* The currently-selected one is marked with the accent and a leading rule
   rather than just bold - at a glance you can see which is live without
   reading the labels. */
.custom-select-option.active {
  background: var(--neutral-bg);
  font-weight: 600;
  box-shadow: inset 2px 0 0 var(--accent);
}

.custom-select-option[hidden] {
  display: none;
}

.custom-select-empty {
  padding: 10px;
  color: var(--text-muted);
  font-size: 0.85rem;
  text-align: center;
}

.date-row {
  display: flex;
  gap: 16px;
  align-items: flex-end;
  flex-wrap: wrap;
}

/* --- Migration progress --- */

/* Transfer stage: MYOB and Xero endpoint glyphs joined by a track, with
   individual file icons flown across it via the Web Animations API
   (TransferAnimation class in app.js) whenever a real attachment upload
   succeeds - driven by an actual count increase from the poll response,
   never a decorative loop. */
.xfer-stage {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(12px, 3vw, 26px);
  margin-top: 2px;
}

.xfer-node {
  flex: 0 0 auto;
  width: 140px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
}

.xfer-glyph {
  position: relative;
  width: 60px;
  height: 60px;
  border-radius: 16px;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  background: var(--bg-elevated);
  box-shadow: 0 1px 2px rgba(18, 22, 28, .05), 0 2px 6px -2px rgba(18, 22, 28, .07);
  transition: border-color .35s ease, background-color .35s ease;
}
.xfer-glyph svg { width: 26px; height: 26px; display: block; }

/* Real logos in the transfer-lane glyphs too, not just the connection
   cards - same myob-logo.png crop technique (see .card-logo-crop). Sized to
   fit comfortably inside the 60px tile with room to spare - it was 68px
   wide before, actually wider than its own 60px box, so it overflowed the
   rounded tile on both sides instead of sitting inside it properly. */
.xfer-logo-crop {
  position: relative;
  display: inline-block;
  overflow: hidden;
}
.xfer-logo-crop--myob {
  width: 49px;
  height: 20px;
}
.xfer-logo-crop--myob img {
  position: absolute;
  left: -10px;
  top: -26px;
  width: 70px;
  height: 65px;
}

.xfer-logo { display: block; }
.xfer-logo--xero { width: 34px; height: 34px; border-radius: 50%; }

.xfer-node--myob .xfer-glyph { color: var(--myob-accent); background: rgba(78, 34, 204, .045); }
.xfer-node--xero .xfer-glyph { color: var(--xero-accent); background: rgba(30, 143, 214, .045); }

.xfer-node--myob.is-sending .xfer-glyph {
  border-color: rgba(78, 34, 204, .38);
  background: rgba(78, 34, 204, .10);
}
.xfer-node--xero.is-receiving .xfer-glyph {
  border-color: rgba(30, 143, 214, .45);
  background: rgba(30, 143, 214, .11);
}

/* Pulses continuously while the tool is still paging through MYOB, before
   individual bills exist to fly as files - a sign of life for that phase. */
.xfer-node--myob.is-fetching .xfer-glyph {
  animation: xfer-fetch-pulse 1.4s ease-in-out infinite;
}
@keyframes xfer-fetch-pulse {
  0%, 100% { box-shadow: 0 1px 2px rgba(18, 22, 28, .05), 0 2px 6px -2px rgba(18, 22, 28, .07); border-color: var(--border); }
  50% { box-shadow: 0 0 0 6px rgba(78, 34, 204, .10); border-color: rgba(78, 34, 204, .4); }
}
@media (prefers-reduced-motion: reduce) {
  .xfer-node--myob.is-fetching .xfer-glyph { animation: none; border-color: rgba(78, 34, 204, .4); }
}

/* pulse ring emitted on arrival */
.xfer-ring {
  position: absolute;
  inset: -1px;
  border-radius: 16px;
  border: 2px solid var(--xero-accent);
  opacity: 0;
  pointer-events: none;
}

/* arrival checkmark badge */
.xfer-check {
  position: absolute;
  right: -7px;
  bottom: -7px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--xero-accent);
  color: #fff;
  display: grid;
  place-items: center;
  box-shadow: 0 2px 6px rgba(30, 143, 214, .35);
  opacity: 0;
  transform: scale(.6);
  transition: opacity .22s ease, transform .3s cubic-bezier(.20, 1.40, .35, 1);
}
.xfer-check svg { width: 12px; height: 12px; }
.xfer-node--xero.is-receiving .xfer-check { opacity: 1; transform: scale(1); }

.xfer-node__name { font-size: 0.875rem; font-weight: 650; letter-spacing: -.01em; line-height: 1.2; color: var(--text); }
.xfer-node__role { font-size: 0.75rem; color: var(--text-muted); margin-top: -4px; }

.xfer-pill {
  font-size: 0.75rem;
  font-weight: 600;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-muted);
  background: var(--bg);
  font-variant-numeric: tabular-nums;
  transition: color .3s ease, border-color .3s ease, background-color .3s ease;
}
.xfer-pill.is-active {
  color: var(--myob-accent);
  border-color: rgba(78, 34, 204, .28);
  background: rgba(78, 34, 204, .06);
}

.xfer-tally { display: flex; align-items: baseline; gap: 6px; }
.xfer-tally__num {
  font-size: 1.6rem;
  font-weight: 660;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--text);
  transition: color .45s ease;
}
.xfer-tally__num.is-hot { color: var(--xero-accent); }
.xfer-tally__unit { font-size: 0.75rem; color: var(--text-muted); }

.xfer-track {
  flex: 1 1 auto;
  position: relative;
  height: 2px;
  min-width: 56px;
  align-self: center;
  margin-bottom: 44px; /* optical centring against the glyph row, not the full node column */
}
.xfer-track__line {
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(to right, var(--border) 0 7px, transparent 7px 15px);
  background-size: 15px 2px;
  transition: opacity .4s ease;
}
.xfer-track.is-flowing .xfer-track__line {
  /* Xero blue while flowing - the direction of travel is toward Xero, so
     the live line reads as "arriving there" rather than as a third colour. */
  background-image: repeating-linear-gradient(to right, rgba(30, 143, 214, .55) 0 7px, transparent 7px 15px);
  animation: xfer-march-x 780ms linear infinite;
}
@keyframes xfer-march-x { from { background-position-x: 0; } to { background-position-x: 15px; } }

/* flying file - positioned/animated entirely via JS Web Animations API */
.xfer-layer { position: absolute; inset: 0; pointer-events: none; z-index: 3; }

.xfer-flyer { position: absolute; left: 0; top: 0; will-change: transform; }
.xfer-flyer__arc { position: absolute; left: 0; top: 0; will-change: transform; }
.xfer-flyer__body { position: absolute; left: -15px; top: -18px; width: 30px; height: 36px; will-change: transform, opacity; }
.xfer-flyer__ico { position: absolute; inset: 0; }
.xfer-flyer__ico svg { width: 30px; height: 36px; display: block; }
.xfer-flyer__ico--from { color: var(--myob-accent); }
.xfer-flyer__ico--to { color: var(--xero-accent); opacity: 0; }

.xfer-trail {
  position: absolute;
  left: -150px;
  top: -8px;
  width: 150px;
  height: 16px;
  transform-origin: 100% 50%;
  pointer-events: none;
}
.xfer-trail__ink {
  display: block;
  width: 100%;
  height: 100%;
  transform-origin: 100% 50%;
  border-radius: 999px;
  opacity: 0;
  background: linear-gradient(to right, rgba(78, 34, 204, 0) 0%, rgba(78, 34, 204, .10) 42%, rgba(30, 143, 214, .24) 100%);
  filter: blur(4px);
}

@media (max-width: 600px) {
  .xfer-stage { flex-direction: column; gap: 0; }
  .xfer-node { width: 100%; }
  .xfer-track {
    flex: 0 0 118px;
    width: 2px;
    height: 118px;
    min-width: 0;
    margin: 16px 0;
    align-self: center;
  }
  .xfer-track__line {
    background-image: repeating-linear-gradient(to bottom, var(--border) 0 7px, transparent 7px 15px);
    background-size: 2px 15px;
  }
  .xfer-track.is-flowing .xfer-track__line {
    background-image: repeating-linear-gradient(to bottom, rgba(30, 143, 214, .55) 0 7px, transparent 7px 15px);
    animation: xfer-march-y 780ms linear infinite;
  }
  @keyframes xfer-march-y { from { background-position-y: 0; } to { background-position-y: 15px; } }
}

@media (prefers-reduced-motion: reduce) {
  .xfer-track, .xfer-glyph, .xfer-check, .xfer-tally__num, .xfer-pill {
    transition-duration: .12s !important;
  }
  .xfer-track.is-flowing .xfer-track__line { animation: none !important; }
}

.progress-row {
  margin: 14px 0 4px;
}

.progress-track {
  height: 10px;
  border-radius: 999px;
  background: var(--neutral-bg);
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
  width: 0%;
  transition: width 0.4s ease;
}

.progress-label {
  margin: 6px 0 0;
  font-size: 0.82rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.counters {
  display: flex;
  gap: 10px;
  margin: 16px 0;
  flex-wrap: wrap;
}

.scope-note {
  color: var(--text-muted);
  font-size: 0.85rem;
  margin: 10px 0 24px;
  line-height: 1.5;
}

.link-toggle {
  background: none;
  border: none;
  padding: 0;
  margin: 0 0 10px;
  color: var(--text-muted);
  font-size: 0.82rem;
  text-decoration: underline;
  cursor: pointer;
}

.link-toggle:hover {
  color: var(--text);
}

.button--info {
  margin-top: 10px;
  color: var(--text-muted);
  border-color: var(--border);
}

.button--info:hover {
  color: var(--text);
}

.button--info svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

/* --- "How bill matching works" dialog --- */

.info-dialog {
  width: min(560px, calc(100vw - 48px));
  max-height: min(680px, calc(100vh - 64px));
  margin: auto;
  padding: 32px;
  border: none;
  border-radius: 20px;
  background: var(--bg-elevated);
  color: var(--text);
  box-shadow: var(--shadow);
  overflow-y: auto;
  /* Styled scrollbar for if content ever does overflow - the bare browser
     default one read as a stray/broken grey line against this design. */
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

.info-dialog::-webkit-scrollbar {
  width: 8px;
}

.info-dialog::-webkit-scrollbar-track {
  background: transparent;
}

.info-dialog::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 999px;
}

.info-dialog::-webkit-scrollbar-thumb:hover {
  background: var(--text-muted);
}

.info-dialog::backdrop {
  background: rgba(18, 22, 28, 0.5);
  backdrop-filter: blur(3px);
}

/* Fallback fade-in for browsers that support ::backdrop but not the
   dialog's built-in open-state transition - purely cosmetic, never blocks
   anything if unsupported. */
.info-dialog[open] {
  animation: info-dialog-in 0.18s ease-out;
}
@keyframes info-dialog-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .info-dialog[open] { animation: none; }
}

.info-dialog-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}

.info-dialog-header h2 {
  font-size: 1.3rem;
}

.info-dialog-close {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--neutral-bg);
  color: var(--text-muted);
  font-size: 1.1rem;
  line-height: 1;
  display: grid;
  place-items: center;
}

.info-dialog-close:hover {
  background: var(--border);
  color: var(--text);
}

.info-dialog-subtitle {
  margin: 10px 0 22px;
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.55;
}

.info-dialog h3 {
  font-size: 0.95rem;
  margin: 26px 0 12px;
}

.info-dialog h3:first-of-type {
  margin-top: 0;
}

.info-dialog > p {
  font-size: 0.88rem;
  color: var(--text-muted);
  line-height: 1.6;
  margin: 0 0 4px;
}

.match-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.match-steps--compact {
  gap: 12px;
}

.match-steps li {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.match-step-num {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 0.75rem;
  font-weight: 700;
  display: grid;
  place-items: center;
  font-variant-numeric: tabular-nums;
}

.match-steps li > div {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-top: 1px;
}

.match-steps li strong {
  font-size: 0.88rem;
  color: var(--text);
}

/* Scoped to the span nested inside the text div specifically, not just any
   span in the li - .match-step-num is also a <span>, a direct child of the
   li itself, and this rule used to accidentally match it too. Since it's
   more specific than .match-step-num's own color rule, it was silently
   overriding the number's white text with this muted grey - nearly
   invisible against the orange circle behind it. */
.match-steps li > div > span {
  font-size: 0.82rem;
  color: var(--text-muted);
  line-height: 1.5;
}

.info-note {
  margin: 18px 0 0;
  padding: 12px 16px;
  border-radius: 12px;
  background: var(--warn-bg);
  color: var(--warn);
  font-size: 0.85rem;
  line-height: 1.55;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  border-radius: 999px;
  background: var(--neutral-bg);
  color: var(--text-muted);
  font-size: 0.82rem;
}

.chip strong {
  color: var(--text);
  font-size: 0.92rem;
  font-variant-numeric: tabular-nums;
}

/* Uploaded/Failed carry their own tint once there's actually a nonzero
   count (see .has-value, toggled in app.js) - a red "Failed 0" sitting
   there by default would read as alarming when nothing has gone wrong yet. */
.chip--uploaded.has-value { background: var(--success-bg); }
.chip--uploaded.has-value strong { color: var(--success); }

.chip--failed.has-value { background: var(--error-bg); }
.chip--failed.has-value strong { color: var(--error); }

/* Terminal-styled log panel - deliberately always dark, independent of the
   page theme, so it reads as a distinct "console" surface (like a real
   terminal window) rather than just another light card. */
.terminal {
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: #1E1E1E;
}

.terminal-titlebar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  background: #2A2A2A;
}

.terminal-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
}

.terminal-dot--red { background: #FF5F57; }
.terminal-dot--yellow { background: #FEBC2E; }
.terminal-dot--green { background: #28C840; }

.terminal-title {
  margin-left: 6px;
  color: #9A9A9A;
  font-size: 0.75rem;
  font-family: var(--font-sans);
}

/* Was #log-panel, but the multi-job rework clones this from a <template>
   using data-role instead of a unique id, so the ID selector silently
   stopped matching anything - the terminal lost its dark background and
   fixed-height scroll, and just rendered as plain stretched black text.
   Scoping to ".terminal pre" instead works regardless of id/data-role. */
.terminal pre {
  background: #1E1E1E;
  color: #D4D4D4;
  border: none;
  border-radius: 0;
  margin: 0;
  padding: 14px 16px;
  max-height: 320px;
  overflow-y: auto;
  overflow-x: hidden;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* Plain-language result banner, shown once a migration finishes - separate
   from the technical terminal log so the outcome is obvious at a glance
   without reading log lines. */
.result-banner {
  margin-top: 16px;
  padding: 14px 18px;
  border-radius: 12px;
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1.5;
}

.result-banner--success {
  background: var(--success-bg);
  color: var(--success);
}

.result-banner--warn {
  background: var(--warn-bg);
  color: var(--warn);
}

.result-banner--error {
  background: var(--error-bg);
  color: var(--error);
}

/* A failure banner carries more than one line: what happened, what to do
   about it, and the raw upstream error tucked away for support. */
.result-banner__headline {
  display: block;
  font-size: 1rem;
}

.result-banner__action {
  margin: 6px 0 0;
  font-weight: 400;
  font-size: 0.88rem;
}


/* Was #summary / #summary ul - same id-vs-data-role mismatch as the
   terminal above, from the multi-job template rework. */
[data-role="summary"] h3 {
  font-size: 1rem;
  margin: 0;
}

/* The history dialog lists a date, a client name and counts per row, which
   needs more width than the matching-info dialog it shares styling with. */
.info-dialog--wide {
  width: min(720px, calc(100vw - 48px));
}

/* Run-migration heading with the History button opposite it. Its own
   modifier rather than reusing .card-header directly, which is tuned for
   the connection cards' logo/badge row. */
.card-header--run {
  align-items: flex-start;
  margin-bottom: 8px;
}

.history-empty {
  color: var(--text-muted);
  font-size: 0.88rem;
  margin: 4px 0 0;
}

.history-download {
  padding: 6px 12px;
  font-size: 0.8rem;
}

/* --- Past migrations --- */

.history-row {
  display: grid;
  grid-template-columns: 110px minmax(0, 1fr) auto;
  gap: 14px;
  align-items: center;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}

.history-row:first-child { border-top: none; }

.history-when {
  font-size: 0.8rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.history-label {
  font-weight: 600;
  font-size: 0.9rem;
  overflow-wrap: anywhere;
}

.history-meta {
  font-size: 0.8rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.history-right {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}

.history-state {
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  white-space: nowrap;
}

.history-state--ok   { background: var(--success-bg); color: var(--success); }
.history-state--warn { background: var(--warn-bg);    color: var(--warn); }
.history-state--bad  { background: var(--error-bg);   color: var(--error); }

@media (max-width: 640px) {
  .history-row { grid-template-columns: minmax(0, 1fr); gap: 6px; }
  .history-right { justify-content: flex-start; }
}

/* Shown in the job panel while a migration is paused waiting on a rate
   limit - the counters stop moving during that, which otherwise reads as a
   hung run. */
.notice {
  margin: 14px 0 0;
  padding: 12px 16px;
  border-radius: 12px;
  font-size: 0.86rem;
  line-height: 1.5;
  border: 1px solid transparent;
}

.notice--warn {
  background: var(--warn-bg);
  color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 22%, transparent);
}

.notice--info {
  background: var(--neutral-bg);
  color: var(--text-muted);
}

.summary-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.failure-count {
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.button--download svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.summary-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 20px;
}

[data-role="failure-list"] {
  margin: 10px 0 0;
  padding-left: 20px;
  font-size: 0.9rem;
  color: var(--text-muted);
}

[data-role="failure-list"] li {
  padding: 2px 0;
}

.failure-badge {
  display: inline-block;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 1px 7px;
  border-radius: 999px;
  margin-right: 6px;
}

.failure-badge--business {
  color: var(--warn);
  background: var(--warn-bg);
}

.failure-badge--technical {
  color: var(--error);
  background: var(--error-bg);
}

.failure-status {
  display: inline-block;
  font-size: 0.78rem;
  font-weight: 600;
  margin-left: 8px;
}

.failure-status--resolved {
  color: var(--success);
}

.failure-status--open {
  color: var(--error);
}

.failure-resolved {
  color: var(--text-muted);
  text-decoration: line-through;
  text-decoration-color: var(--border);
}

.retry-status {
  margin: 10px 0 0;
  font-size: 0.85rem;
  color: var(--text-muted);
  font-weight: 600;
}
