/* ============================================================
   NextERP — Button system (single source of truth)
   ============================================================
   Every .btn rule that used to live scattered through app.css
   (base, :disabled, :active, variants, .is-busy, spinners) now
   lives here. app.css keeps only *contextual* overrides —
   .modal-foot .btn, .tenant-actions .btn, .lp-final-cta-inner
   .btn and so on — which are more specific and still win.

   Loaded straight after app.css, so every var() from app.css's
   :root exists. No new palette colours are invented: the only
   new tokens are button-scoped aliases plus two pairs that had
   to be fixed per theme for contrast (see --btn-danger-ink and
   --btn-link-ink below).

   Six states, in this order of precedence:
     default -> :hover -> :focus-visible -> :active
             -> .is-loading / [aria-busy="true"] -> :disabled
   Disabled wins over everything; loading wins over hover/active.
   ============================================================ */

:root{
  --btn-radius:11px;
  --btn-radius-sm:8px;
  --btn-pad:10px 16px;
  --btn-font-size:13px;
  --btn-transition:all 0.15s ease-in-out;

  /* Hover/active are brightness shifts, per spec: ~10% darker on
     hover, ~20% on press. Held in tokens so a variant that must
     not be darkened (the white CTA on the indigo landing band)
     can opt out with one declaration instead of a !important. */
  --btn-hover-bright:0.9;
  --btn-active-bright:0.8;
  --btn-active-scale:0.98;

  /* Ring colour, not accent: with outline-offset the ring sits in
     the page gap, so it has to read against the button fill AND
     the page. Near-black on light, near-white on dark clears both. */
  --btn-focus-ring:#1B2127;
  --btn-focus-width:2px;
  --btn-focus-offset:2px;

  /* --btn-ink is both the label colour and the spinner colour, so
     the two can never disagree. Each variant sets it. */
  --btn-ink:var(--ink);

  --btn-primary-bg:var(--accent);
  --btn-primary-ink:#FFFFFF;          /* 6.08:1 on #5A4FCF */
  --btn-secondary-bg:var(--surface-soft);
  --btn-secondary-bg-hover:var(--surface-hover);
  --btn-secondary-ink:var(--ink);      /* 15.09:1 */
  --btn-secondary-edge:var(--panel-edge);
  --btn-outline-bg:var(--panel);
  --btn-outline-ink:var(--ink-soft);
  --btn-danger-bg:var(--danger);
  --btn-danger-ink:#FFFFFF;            /* 5.90:1 on #B23A3A */
  --btn-link-ink:#4A40B8;              /* 6.74:1 on --bg; --accent itself is 5.30:1 */

  --btn-disabled-bg:var(--pill-gray-bg);
  --btn-disabled-ink:var(--pill-gray-text);
  --btn-disabled-edge:var(--panel-edge);
  --btn-disabled-opacity:0.6;

  --btn-spin-speed:0.6s;
}

[data-theme="dark"]{
  --btn-focus-ring:#F2F3F5;
  /* Darkening a near-black surface by 10% is invisible, so dark
     mode lifts instead of darkens. Same magnitude, same feedback,
     opposite direction. */
  --btn-hover-bright:1.14;
  --btn-active-bright:1.26;
  /* White on dark mode's lighter --danger (#F2666E) is only
     3.04:1 and fails AA. Ink on it is 5.34:1. */
  --btn-danger-ink:#1B2127;
  /* --accent in dark mode is 4.10:1 on --bg, under AA for body
     text. --steel-dark (#9AA6FA) is 7.90:1. */
  --btn-link-ink:#9AA6FA;
  --btn-disabled-bg:var(--pill-gray-bg);
  --btn-disabled-ink:var(--pill-gray-text);
}

/* ============================================================
   1. Default (enabled)
   ============================================================ */
.btn{
  position:relative;              /* anchor for the loading spinner */
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:6px;
  font-family:'Inter',sans-serif;
  font-weight:600;
  font-size:var(--btn-font-size);
  line-height:1.2;
  /* Transparent 1px border rather than border:none: a filled
     .btn-primary and a bordered .btn-ghost sitting side by side
     in a modal footer are now the same height. */
  border:1px solid transparent;
  border-radius:var(--btn-radius);
  padding:var(--btn-pad);
  background:var(--panel);
  color:var(--btn-ink);
  cursor:pointer;
  text-decoration:none;           /* .btn is also used on <a> */
  user-select:none;
  -webkit-tap-highlight-color:transparent;
  touch-action:manipulation;
  transition:var(--btn-transition);
}
.btn svg{ flex:0 0 auto; }

/* ============================================================
   2. Hover
   ============================================================ */
.btn:hover{
  filter:brightness(var(--btn-hover-bright));
}

/* ============================================================
   3. Keyboard focus
   :focus-visible only, so a mouse press never draws the ring but
   Tab always does.
   ============================================================ */
.btn:focus-visible{
  outline:var(--btn-focus-width) solid var(--btn-focus-ring);
  outline-offset:var(--btn-focus-offset);
}

/* ============================================================
   4. Active / pressed
   ============================================================ */
.btn:active:not(:disabled):not([aria-disabled="true"]):not(.is-loading){
  filter:brightness(var(--btn-active-bright));
  transform:scale(var(--btn-active-scale));
}

/* ============================================================
   5. Loading / busy
   Text stays in the accessibility tree and keeps its box, so the
   button does not resize mid-request — it is only made invisible.
   Pair the class with aria-busy="true" in markup; either alone
   also works.
   ============================================================ */
.btn.is-loading,
.btn[aria-busy="true"]{
  pointer-events:none;
  color:transparent;
  filter:none;
  transform:none;
  cursor:progress;
}
/* color:transparent handles bare text nodes; icons and <span>s
   with their own fill need hiding outright. */
.btn.is-loading > *,
.btn[aria-busy="true"] > *{
  visibility:hidden;
}
.btn.is-loading::after,
.btn[aria-busy="true"]::after{
  content:"";
  position:absolute;
  top:50%;
  inset-inline-start:50%;
  width:1.05em;
  height:1.05em;
  margin-top:-0.525em;
  margin-inline-start:-0.525em;
  border-radius:50%;
  /* var(--btn-ink), not currentColor: currentColor is transparent
     in this state. */
  border:2px solid var(--btn-ink);
  border-top-color:transparent;
  animation:btn-spin var(--btn-spin-speed) linear infinite;
}

/* ============================================================
   6. Disabled
   Real :disabled, plus [aria-disabled] and .is-disabled for
   <a class="btn"> and for buttons that must stay focusable so a
   screen reader can still reach the explanation. Note that
   aria-disabled alone does NOT block the click — the handler
   must return early as well.
   ============================================================ */
.btn:disabled,
.btn[aria-disabled="true"],
.btn.is-disabled{
  background:var(--btn-disabled-bg);
  border-color:var(--btn-disabled-edge);
  color:var(--btn-disabled-ink);
  opacity:var(--btn-disabled-opacity);
  cursor:not-allowed;
  box-shadow:none;
}
/* Every interactive affordance off — no brightness shift, no
   press, no lift from a contextual :hover rule elsewhere. */
.btn:disabled:hover,
.btn:disabled:active,
.btn[aria-disabled="true"]:hover,
.btn[aria-disabled="true"]:active,
.btn.is-disabled:hover,
.btn.is-disabled:active{
  filter:none;
  transform:none;
  box-shadow:none;
  background:var(--btn-disabled-bg);
  color:var(--btn-disabled-ink);
  text-decoration:none;
}
/* An <a> cannot be disabled, so swallow the click. */
a.btn[aria-disabled="true"],
a.btn.is-disabled{ pointer-events:none; }

/* Loading AND disabled at the same time is the normal case — a submit
   handler sets .disabled to block a double-submit and .is-loading to show
   why. Without this the disabled rule above, being later, would put the
   label's colour back and you would see the text and the spinner at once.
   The spinner is re-tinted to the muted ink so it stays visible on the
   greyed fill. */
.btn.is-loading:disabled,
.btn.is-loading[aria-disabled="true"],
.btn.is-loading.is-disabled,
.btn[aria-busy="true"]:disabled,
.btn[aria-busy="true"][aria-disabled="true"],
.btn[aria-busy="true"].is-disabled{
  --btn-ink:var(--btn-disabled-ink);
  color:transparent;
  cursor:progress;
}

/* ============================================================
   Variants
   ============================================================ */
.btn-primary{
  --btn-ink:var(--btn-primary-ink);
  background:var(--btn-primary-bg);
  border-color:var(--btn-primary-bg);
  color:var(--btn-ink);
}

/* .btn-secondary and .btn-ghost are the same control; .btn-ghost
   is the name already used in ~116 places, .btn-secondary is the
   name to use in new markup. */
.btn-secondary,
.btn-ghost{
  --btn-ink:var(--btn-secondary-ink);
  background:var(--btn-secondary-bg);
  border-color:var(--btn-secondary-edge);
  color:var(--btn-ink);
}
.btn-secondary:hover,
.btn-ghost:hover{ background:var(--btn-secondary-bg-hover); }

/* Solid destructive. Scoped to .btn.btn-danger on purpose: bare
   .btn-danger (21 existing call sites) is a borderless text
   button and is kept that way further down. */
.btn.btn-danger,
.btn.btn-danger-solid{
  --btn-ink:var(--btn-danger-ink);
  background:var(--btn-danger-bg);
  border-color:var(--btn-danger-bg);
  color:var(--btn-ink);
  font-size:var(--btn-font-size);
  padding:var(--btn-pad);
  text-decoration:none;
}
.btn.btn-danger:hover,
.btn.btn-danger-solid:hover{ text-decoration:none; }

/* Destructive without the shout: no fill until hover, so Save
   stays the only filled control on a modal footer. */
.btn.btn-danger-quiet{
  --btn-ink:var(--danger);
  background:transparent;
  color:var(--btn-ink);
  /* Plain border first: an engine without color-mix() drops the
     next line and keeps this one, rather than rendering no border. */
  border:1px solid var(--panel-edge);
  border-color:color-mix(in srgb, var(--danger) 38%, transparent);
}
.btn.btn-danger-quiet:hover{ background:var(--error-bg); border-color:var(--danger); }

.btn-outline{
  --btn-ink:var(--btn-outline-ink);
  background:var(--btn-outline-bg);
  border-color:var(--panel-edge);
  color:var(--btn-ink);
}
.btn-outline:hover{ background:var(--bg); }

/* A secondary action that must line up with Cancel and Save but
   not compete with them. Same height, lighter voice. */
.btn.btn-quiet{
  --btn-ink:var(--ink-soft);
  color:var(--btn-ink);
  font-weight:600;
}
.btn.btn-quiet:hover{ --btn-ink:var(--ink); color:var(--btn-ink); border-color:var(--ink-soft); }

/* ============================================================
   Sizes and layout modifiers
   ============================================================ */
.btn.btn-sm{ padding:5px 10px; font-size:11.5px; border-radius:var(--btn-radius-sm); }
.btn.btn-lg{ padding:13px 26px; font-size:14.5px; border-radius:12px; }
.btn.btn-block{ display:flex; width:100%; }
.btn.btn-icon{ padding:9px 10px; gap:0; }

/* ============================================================
   Text-style buttons
   .btn-link, .btn-linkish and bare .btn-danger are used WITHOUT
   .btn and must stay borderless and unpadded — a brightness
   shift and a downscale read as broken on a text link, so they
   get colour, underline and opacity feedback instead, plus the
   same focus ring, disabled treatment and spinner.
   ============================================================ */
.btn-link,
.btn-linkish,
.btn-danger:not(.btn){
  position:relative;
  background:none;
  border:none;
  padding:0;
  font-family:'Inter',sans-serif;
  font-weight:600;
  cursor:pointer;
  transition:var(--btn-transition);
}
.btn-link{ --btn-ink:var(--btn-link-ink); color:var(--btn-ink); font-size:12.5px; }
.btn-linkish{
  --btn-ink:var(--btn-link-ink);
  color:var(--btn-ink);
  font-family:inherit;
  font-size:11.5px;
  text-decoration:underline;
  text-underline-offset:2px;
}
.btn-linkish.danger{ --btn-ink:var(--rust); color:var(--btn-ink); }
.btn-danger:not(.btn){
  --btn-ink:var(--danger);
  color:var(--btn-ink);
  font-size:13px;
  padding:8px 4px;
}

.btn-link:hover,
.btn-danger:not(.btn):hover{ text-decoration:underline; }
.btn-linkish:hover{ --btn-ink:var(--accent-dark); color:var(--btn-ink); }
[data-theme="dark"] .btn-linkish:hover{ --btn-ink:#B9C2FC; color:var(--btn-ink); }
.btn-linkish.danger:hover{ --btn-ink:var(--rust-dark); color:var(--btn-ink); }

.btn-link:focus-visible,
.btn-linkish:focus-visible,
.btn-danger:not(.btn):focus-visible{
  outline:var(--btn-focus-width) solid var(--btn-focus-ring);
  outline-offset:var(--btn-focus-offset);
  border-radius:4px;
}
.btn-link:active,
.btn-linkish:active,
.btn-danger:not(.btn):active{ opacity:0.7; }

.btn-link.is-loading,
.btn-link[aria-busy="true"],
.btn-linkish.is-loading,
.btn-linkish[aria-busy="true"],
.btn-danger:not(.btn).is-loading,
.btn-danger:not(.btn)[aria-busy="true"]{
  pointer-events:none;
  color:transparent;
  text-decoration:none;
  cursor:progress;
}
.btn-link.is-loading::after,
.btn-link[aria-busy="true"]::after,
.btn-linkish.is-loading::after,
.btn-linkish[aria-busy="true"]::after,
.btn-danger:not(.btn).is-loading::after,
.btn-danger:not(.btn)[aria-busy="true"]::after{
  content:"";
  position:absolute;
  top:50%;
  inset-inline-start:50%;
  width:1em;
  height:1em;
  margin-top:-0.5em;
  margin-inline-start:-0.5em;
  border-radius:50%;
  border:2px solid var(--btn-ink);
  border-top-color:transparent;
  animation:btn-spin var(--btn-spin-speed) linear infinite;
}

.btn-link:disabled,
.btn-link[aria-disabled="true"],
.btn-linkish:disabled,
.btn-linkish[aria-disabled="true"],
.btn-danger:not(.btn):disabled,
.btn-danger:not(.btn)[aria-disabled="true"]{
  opacity:var(--btn-disabled-opacity);
  cursor:not-allowed;
  text-decoration:none;
}
.btn-link:disabled:hover,
.btn-link[aria-disabled="true"]:hover,
.btn-linkish:disabled:hover,
.btn-linkish[aria-disabled="true"]:hover,
.btn-danger:not(.btn):disabled:hover,
.btn-danger:not(.btn)[aria-disabled="true"]:hover{
  text-decoration:none;
  filter:none;
  opacity:var(--btn-disabled-opacity);
}

/* ============================================================
   Keyboard focus for every other button on the site
   The ~40 bespoke classes (.icon-btn, .lane-icon-btn,
   .hamburger-btn, .board-switch-btn, .hr-tab-btn, .connect-btn,
   .view-toggle button …) are all real <button> elements, so one
   rule gives the whole site a visible focus ring without
   touching any of their individual styling.
   ============================================================ */
button:focus-visible,
summary:focus-visible,
[role="button"]:focus-visible,
input[type="submit"]:focus-visible,
input[type="button"]:focus-visible{
  outline:var(--btn-focus-width) solid var(--btn-focus-ring);
  outline-offset:var(--btn-focus-offset);
}
/* A file-input label styled as a button: the ring has to follow
   the hidden input's focus. */
label.file-upload-btn:focus-within{
  outline:var(--btn-focus-width) solid var(--btn-focus-ring);
  outline-offset:var(--btn-focus-offset);
}
/* Baseline for the bespoke classes only — the per-class
   :disabled rules in app.css are two classes deep and still win
   where they set something deliberately different (.icon-btn
   stays at .25). */
button:disabled{ cursor:not-allowed; }

/* ============================================================
   Legacy busy hooks — kept working, unchanged
   app.js's withBusy() sets .is-busy and injects
   <span class="btn-spinner-sm"> plus a label, i.e. it supplies
   its own spinner and its own text. That must NOT collide with
   the .is-loading state above, so .is-busy stays a dim-and-wait
   treatment and nothing more. New code should prefer
   .is-loading + aria-busy="true" and leave the label alone.
   ============================================================ */
.btn.is-busy,
button.is-busy{ opacity:0.8; cursor:progress; }
.btn-spinner{
  width:15px; height:15px; flex:0 0 auto; border-radius:50%;
  border:2px solid var(--panel-edge); border-top-color:var(--accent);
  animation:btn-spin 0.7s linear infinite;
}
.nav-item.active .btn-spinner{ border-color:rgba(255,255,255,.35); border-top-color:#fff; }
.btn-spinner-sm{
  display:inline-block; width:12px; height:12px; margin-inline-end:7px;
  vertical-align:-1px; border-radius:50%; border:2px solid currentColor;
  border-top-color:transparent; animation:btn-spin 0.7s linear infinite; opacity:.85;
}

/* ============================================================
   Motion, RTL and forced colours
   ============================================================ */
@keyframes btn-spin{ to{ transform:rotate(360deg); } }

@media (prefers-reduced-motion:reduce){
  .btn,
  .btn-link,
  .btn-linkish,
  .btn-danger:not(.btn){ transition:none; }
  .btn:active:not(:disabled):not([aria-disabled="true"]):not(.is-loading){ transform:none; }
  /* The spinner is state, not decoration, so it keeps turning —
     just slowly enough not to trigger anyone. */
  .btn.is-loading::after,
  .btn[aria-busy="true"]::after,
  .btn-spinner,
  .btn-spinner-sm{ animation-duration:1.6s; }
}

/* Windows high-contrast: filters and opacity are ignored there,
   so state has to come from the system palette. */
@media (forced-colors:active){
  .btn{ border:1px solid ButtonBorder; }
  .btn:focus-visible,
  button:focus-visible{ outline:2px solid Highlight; outline-offset:2px; }
  .btn:disabled,
  .btn[aria-disabled="true"],
  .btn.is-disabled{ color:GrayText; border-color:GrayText; opacity:1; }
  .btn.is-loading::after,
  .btn[aria-busy="true"]::after{ border-color:CanvasText; border-top-color:transparent; }
}
