/* ============================================================================
   SayPays Web OS — Leaf Components (CSS Component Layer)
   web/css/components.css   — FINAL (verdicts incorporated). Reproduced verbatim.
   ============================================================================ */

/* ---- BUTTON base ---- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-h);              /* 40 → 44px touch (tokens.css) */
  min-width: max-content;
  padding-inline: var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  line-height: var(--leading-none);
  white-space: nowrap; cursor: pointer; user-select: none; -webkit-user-select: none;
  text-decoration: none;
  transition:
    background-color var(--motion-fast) var(--ease-standard),
    color            var(--motion-fast) var(--ease-standard),
    border-color     var(--motion-fast) var(--ease-standard),
    box-shadow       var(--motion-fast) var(--ease-standard);
}
/* Solid --color-primary-ring (NOT the 40% alpha ring): clears 3:1 non-text bar
   on both fill and page bg without depending on color-mix. Don't regress. */
.btn:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-primary-ring);
  outline-offset: var(--focus-ring-offset);
}
/* primary (indigo) */
.btn-primary { background-color: var(--color-primary); color: var(--color-on-primary); border-color: transparent; }
.btn-primary:hover { background-color: var(--color-primary-hover); }
.btn-primary:active { background-color: var(--color-primary-hover); }
/* secondary (white + border) */
.btn-secondary { background-color: var(--surface-card); color: var(--text-default); border-color: var(--border-strong); }
.btn-secondary:hover { background-color: var(--surface-subtle); border-color: var(--border-strong); }
.btn-secondary:active { background-color: var(--surface-locked); }
/* danger (rose) — C2#6 destructive commit/void/refund */
.btn-danger { background-color: var(--color-danger); color: var(--color-on-danger); border-color: transparent; }
.btn-danger:hover { background-color: var(--color-danger-hover); }
.btn-danger:active { background-color: var(--color-danger-hover); }
.btn-danger:focus-visible { outline-color: var(--color-danger); }
/* ghost (slate-600 text per A4 = --color-info) */
.btn-ghost { background-color: transparent; color: var(--color-info); border-color: transparent; }
.btn-ghost:hover { background-color: var(--surface-locked); }
.btn-ghost:active { background-color: var(--surface-locked); }
/* size / shape modifiers */
.btn-block { width: 100%; }
.btn-icon { width: var(--hit-target-min); min-width: var(--hit-target-min); padding-inline: 0; }
/* touch inline-axis 44px floor — short text labels ("OK"/1-char) can be <44px wide
   even though min-height already resolves to 44px on touch. Same condition tokens.css
   uses for --control-h. */
@media (pointer: coarse), (max-width: 1023px) {
  .btn { min-width: var(--hit-target-min); }
}
/* disabled-with-reason (A4/A8#10) — slate-100 bg + slate-600 AA text + :data-tip + :aria-label */
.btn:disabled, .btn[disabled], .btn[aria-disabled="true"] {
  background-color: var(--surface-locked); color: var(--text-locked);
  border-color: transparent; cursor: not-allowed; box-shadow: none;
}
.btn:disabled:hover, .btn[disabled]:hover, .btn[aria-disabled="true"]:hover {
  background-color: var(--surface-locked); border-color: transparent;
}
/* suppress active-looking focus ring on a still-focusable (aria-disabled) inert button */
.btn:disabled:focus-visible, .btn[disabled]:focus-visible, .btn[aria-disabled="true"]:focus-visible {
  outline: none;
}
/* loading (A4 spinner replaces label, keep width). is-loading is NOT keyboard-inert
   on its own — call site MUST also bind :disabled/:aria-disabled + :aria-busy. */
.btn.is-loading { position: relative; color: transparent !important; pointer-events: none; }
/* default spinner = --color-info (slate-600), VISIBLE on neutral/transparent so an
   un-overridden/future variant degrades to a visible (not invisible white) spinner. */
.btn.is-loading::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 1rem; height: 1rem; border-radius: var(--radius-full);
  border: 2px solid var(--color-info); border-top-color: transparent;
  animation: btn-spin var(--motion-base) linear infinite;
}
.btn-primary.is-loading::after   { border-color: var(--color-on-primary); border-top-color: transparent; }
.btn-danger.is-loading::after    { border-color: var(--color-on-danger);  border-top-color: transparent; }
.btn-secondary.is-loading::after { border-color: var(--text-default);     border-top-color: transparent; }
.btn-ghost.is-loading::after     { border-color: var(--color-info);       border-top-color: transparent; }
@keyframes btn-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .btn.is-loading::after { animation: none; } }
.btn > svg { width: 1em; height: 1em; flex: none; }

/* ---- STATUSPILL base ---- */
.pill {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding-block: 0.125rem;                   /* py-0.5 (2px) A4 exact; no --space-0.5 token, geometric literal */
  padding-inline: var(--space-2);
  border-radius: var(--radius-full);
  font-family: var(--font-sans); font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold); line-height: var(--leading-tight);
  white-space: nowrap; max-width: 100%;
}
.pill__icon { width: 0.875rem; height: 0.875rem; flex: none; }
.pill__icon svg, .pill > svg { width: 100%; height: 100%; display: block; }
.pill__label { font-weight: var(--font-weight-semibold); }
.pill__sep { opacity: 0.6; font-weight: var(--font-weight-normal); }
.pill__secondary { font-weight: var(--font-weight-normal); }
/* tone classes (-50 surface / -700 on-surface AA pairs) */
.pill-success    { background-color: var(--color-success-surface);    color: var(--color-on-success-surface); }
.pill-warning    { background-color: var(--color-warning-surface);    color: var(--color-on-warning-surface); }
.pill-danger     { background-color: var(--color-danger-surface);     color: var(--color-on-danger-surface); }
.pill-info       { background-color: var(--color-info-surface);       color: var(--color-on-info-surface); }
.pill-inprogress { background-color: var(--color-inprogress-surface); color: var(--color-on-inprogress-surface); }
.pill-terminal   { background-color: var(--color-info-surface);       color: var(--color-on-info-surface); }
/* composite 2-tone secondary (C2#3). CROSS-TONE: text of one tone over surface of
   another; recomputed floor across all base×secondary = 4.99:1 (AA). Holds only on
   the light -50/-100 surface invariant — re-verify if a tone/theme darkens surfaces. */
.pill__secondary--warning { color: var(--color-on-warning-surface); }
.pill__secondary--danger  { color: var(--color-on-danger-surface); }
.pill__secondary--success { color: var(--color-on-success-surface); }
.pill__secondary--info    { color: var(--color-on-info-surface); }

/* =============================================================================
   TOAST — A7 (5-state) + A4 (Toast) + C1#2.  Append to web/css/components.css.
   Token-only (no raw hex); class-scoped (.toast*); no global restyle.
   Container = fixed top-right stack; cards = per-variant -50 surface / -700
   on-surface (same AA pairs as .pill) + a left accent stripe in the solid tone
   (colour is NEVER the only signal: icon + text + stripe + sr-only state word).
   ============================================================================= */

/* ---- region (rendered ONCE near </body>); stack top-right, newest at bottom -- */
.toast-region {
  position: fixed;
  top: var(--space-4);
  right: var(--space-4);
  z-index: var(--z-toast);                  /* 60 — above drawer/modal/palette */
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: min(22rem, calc(100vw - 2 * var(--space-4)));
  max-height: calc(100vh - 2 * var(--space-4));
  overflow: hidden;                         /* don't scroll the page; oldest evicted by MAX */
  pointer-events: none;                     /* let clicks fall through empty gaps … */
}
.toast-region > * { pointer-events: auto; } /* … but each card is interactive */
/* the two live regions stack as one column (both fixed at the same corner) */
.toast-region--assertive { /* assertive (danger) shares the same corner */ }
/* mobile: full-width top banner stack (A6 touch-first) */
@media (max-width: 640px) {
  .toast-region { left: var(--space-3); right: var(--space-3); top: var(--space-3); width: auto; }
}

/* visually-hidden state prefix the SR reads ("Error: " etc.) — kept out of the
   visual layout but present in the accessibility tree (C1#2). Scoped to .toast. */
.toast__sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}

/* ---- card ---- */
.toast {
  display: grid;
  grid-template-columns: auto 1fr auto;     /* icon · body · dismiss */
  align-items: start;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--surface-card-border);
  border-left-width: 3px;                   /* tone accent stripe (non-colour signal) */
  border-radius: var(--radius-lg);
  background-color: var(--surface-overlay);  /* white base; tinted per-variant below */
  box-shadow: var(--shadow-lg);             /* A4 elevation */
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--text-strong);
}
/* a persistent danger/retry card receives focus on insert (JS sets _focusId);
   it carries tabindex=-1 so .focus() works without making it a Tab stop. */
.toast:focus { outline: none; }
.toast:focus-visible {
  outline: var(--focus-ring-width) solid currentColor;
  outline-offset: var(--focus-ring-offset);
}
.toast__icon { width: 1.25rem; height: 1.25rem; flex: none; margin-top: 0.0625rem; }
.toast__icon svg { width: 100%; height: 100%; display: block; }
.toast__body { min-width: 0; }
.toast__msg { overflow-wrap: anywhere; }    /* long RPC names wrap, don't overflow */
.toast__actions {
  grid-column: 2 / 3;                        /* actions sit under the message, aligned to body */
  display: flex; flex-wrap: wrap; gap: var(--space-3);
  margin-top: var(--space-2);
}
/* inline text action (Undo / Retry / link) — underlined, inherits the tone colour */
.toast__action {
  appearance: none; background: none; border: 0; padding: 0;
  font: inherit; font-weight: var(--font-weight-semibold);
  color: currentColor; text-decoration: underline; text-underline-offset: 2px;
  cursor: pointer;
}
.toast__action:focus-visible {
  outline: var(--focus-ring-width) solid currentColor;
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-sm);
}
/* dismiss (✕) — top-right, icon-only */
.toast__dismiss {
  grid-column: 3 / 4; grid-row: 1;
  appearance: none; background: none; border: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem; padding: 0; margin: -0.125rem -0.25rem 0 0;
  color: inherit; opacity: 0.7; cursor: pointer; border-radius: var(--radius-sm);
}
.toast__dismiss:hover { opacity: 1; }
.toast__dismiss:focus-visible {
  outline: var(--focus-ring-width) solid currentColor;
  outline-offset: 1px;
}
.toast__dismiss svg { width: 1rem; height: 1rem; display: block; }

/* ---- per-variant tone: tinted surface + on-surface text + accent stripe ----
   (mirrors the .pill AA-verified -50/-700 pairs). */
.toast--success { background-color: var(--color-success-surface); color: var(--color-on-success-surface); border-left-color: var(--color-success); }
.toast--warning { background-color: var(--color-warning-surface); color: var(--color-on-warning-surface); border-left-color: var(--color-warning); }
.toast--danger  { background-color: var(--color-danger-surface);  color: var(--color-on-danger-surface);  border-left-color: var(--color-danger); }
.toast--info    { background-color: var(--color-info-surface);    color: var(--color-on-info-surface);    border-left-color: var(--color-info); }
/* the icon takes the SOLID tone for a touch more saliency than body text */
.toast--success .toast__icon { color: var(--color-success); }
.toast--warning .toast__icon { color: var(--color-warning); }
.toast--danger  .toast__icon { color: var(--color-danger); }
.toast--info    .toast__icon { color: var(--color-info); }

/* ---- enter / leave (x-transition class hooks; reduced-motion safe) ----
   Under prefers-reduced-motion, tokens.css globally clamps transition-duration
   to 0.01ms. Alpine v3 does NOT key x-transition completion off the
   transitionend EVENT — it reads the computed transition-duration+delay via
   getComputedStyle and schedules a setTimeout(duration+delay) to run the
   after-leave cleanup. With the 0.01ms clamp that timer elapses on the next
   tick, so the leave cleanup + list splice still run (node IS removed) — removal
   does not depend on transitionend firing. The slide/fade is opacity + a small
   translateX so the instant-swap fallback has no layout jump; the JS auto-dismiss
   timers are setTimeout, independent of CSS, so they fire identically either way.
   No extra per-component reduced-motion rule needed. */
.toast-enter        { transition: opacity var(--motion-base) var(--ease-standard),
                                   transform var(--motion-base) var(--ease-standard); }
.toast-enter-start  { opacity: 0; transform: translateX(0.75rem); }
.toast-enter-end    { opacity: 1; transform: translateX(0); }
.toast-leave        { transition: opacity var(--motion-base) var(--ease-exit),
                                   transform var(--motion-base) var(--ease-exit); }
.toast-leave-start  { opacity: 1; transform: translateX(0); }
.toast-leave-end    { opacity: 0; transform: translateX(0.75rem); }

/* =============================================================================
   EMPTYSTATE — Blueprint A4 (EmptyState) + A7 (5-state contract, state #2 "Empty").
   Token-only (zero hardcoded hex); class-scoped (.empty-state*); NO global restyle.

   WHAT IT IS: the "no data yet, here's how to start" state — a centered block of
   { faint large icon · human headline · ONE line of guidance · ONE primary CTA }.
   Use ONLY when the fetch SUCCEEDED and returned zero rows. NOT for loading
   (skeleton) and NOT for error (danger banner + Retry) — those are separate
   components by design (A7); see the GUARD at the bottom.

   PLACEMENT: works in BOTH hosts with no variant —
     (a) inside a table cell:  <tr><td colspan="N"><div class="empty-state">…
     (b) inside a plain <div> region (card body / drawer / list panel).
   Centering is self-contained (flex column + center) so it never relies on the
   parent being flex/grid or on colspan math.
   ============================================================================= */

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  /* block padding > inline so a narrow colspan cell never forces awkward wrap */
  padding-block: var(--space-8);          /* 32px top/bottom */
  padding-inline: var(--space-6);         /* 24px sides */
  gap: var(--space-3);                    /* 12px rhythm: icon/headline/guidance/CTA */
  max-width: 24rem;                       /* cap the readable measure to ~2 short lines */
  margin-inline: auto;                    /* center the capped block in a wide cell/div */
  color: var(--text-muted);               /* default tone for icon + guidance (A4 'จาง') */
  font-family: var(--font-sans);
}

/* faint, large illustrative icon (inline Lucide stroke SVG, currentColor). */
.empty-state__icon {
  width: 2.5rem;                          /* 40px — large but not dominating a cell */
  height: 2.5rem;
  color: var(--text-muted);
  opacity: 0.55;                          /* faded beyond muted; opacity stays themeable */
}
.empty-state__icon svg {
  width: 100%; height: 100%; display: block;
  stroke-width: 1.5;                      /* thinner stroke = lighter, "faint" feel */
}

/* human headline — the resolved-i18n sentence, never a raw key. Strongest text
   in the block but still calm (no danger colour). */
.empty-state__headline {
  color: var(--text-strong);              /* A4: headline = primary/strong text */
  font-size: var(--text-sm);              /* 14px — body/H2 size; not a page title */
  font-weight: var(--font-weight-semibold);
  line-height: var(--leading-tight);
  margin: 0;
}

/* ONE line of guidance — quiet, explains how to populate the list. */
.empty-state__guidance {
  color: var(--text-muted);               /* A4: guidance = muted */
  font-size: var(--text-xs);              /* 12px meta */
  line-height: var(--leading-normal);
  margin: 0;
  text-wrap: balance;                     /* even two-line wrap; harmless where unsupported */
}

/* CTA slot — holds ONE .btn.btn-primary calling the list's existing add handler.
   margin-top adds a touch more separation than the base gap so the action reads
   as the terminal call-to-action, not a fourth text line. */
.empty-state__cta {
  margin-top: var(--space-1);             /* +4px beyond the 12px gap */
  display: flex;
  justify-content: center;
}

/* DENSITY: in a compact table the empty region shouldn't eat as much vertical
   space — trim block padding only. Inline padding + max-width unchanged. */
html[data-density="compact"] .empty-state {
  padding-block: var(--space-6);          /* 24px */
}

/* GUARD — do NOT add a .empty-state--error or .empty-state--loading variant.
   Error = danger banner (+ RPC name + Retry); Loading = skeleton in the real
   layout. Folding either in here re-creates the single overloaded "msg" the
   redesign is removing. A red EmptyState or a spinner EmptyState is the WRONG
   component — reach for the danger banner or the skeleton instead. */
