/* ============================================================================
 * TMA (Telegram Mini App) styles
 * Consumes web/tokens.css (DESIGN.md). Telegram theme variables take
 * precedence on platforms that expose them (e.g. via WebApp.onEvent).
 * ============================================================================ */

/* Optional: pull Telegram theme params into CSS vars.
   These are set at runtime by tma.js from window.Telegram.WebApp.themeParams.
   If absent, our :root tokens (light) or @media dark win. */
:root {
  --tg-bg: var(--color-surface);
  --tg-text: var(--color-text);
  --tg-muted: var(--color-text-muted);
  --tg-card: var(--color-surface-2);
  --tg-accent: var(--color-tg-blue-strong);
  --tg-on-accent: #FFFFFF;
}

@media (prefers-color-scheme: dark) {
  :root {
    --tg-bg: var(--color-ink);
    --tg-text: var(--color-text-inverse);
    --tg-muted: #94A3B8;
    --tg-card: #1E293B;
    --tg-accent: var(--color-tg-blue);
    --tg-on-accent: #FFFFFF;
  }
}

/* Phase 78 (jun15) T1.4: Telegram-driven dark mode.
   When the user picks dark in Telegram (independent of OS),
   tg.colorScheme === "dark". We toggle a class on <body> so
   CSS can react to Telegram's theme even when the host OS is light.
   The class wins over :root vars but is overridden by inline
   themeParams (set by applyTheme), so Telegram colors always
   take precedence. */
body.theme-dark {
  --tg-bg: var(--color-ink);
  --tg-text: var(--color-text-inverse);
  --tg-muted: #94A3B8;
  --tg-card: #1E293B;
  --tg-accent: var(--color-tg-blue);
  --tg-on-accent: #FFFFFF;
}

body {
  margin: 0;
  padding: 0;
  font-family: var(--font-sans);
  background: var(--tg-bg);
  color: var(--tg-text);
  line-height: 1.4;
  font-feature-settings: "cv11", "ss01";
}

header.tma-header {
  padding: var(--space-4);
  text-align: center;
}
header.tma-header h1 {
  margin: 0 0 var(--space-1);
  font-size: var(--text-xl);
  font-weight: 700;
}

main.tma-main {
  padding: var(--space-2) var(--space-3) var(--space-20);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.card {
  background: var(--tg-card);
  border-radius: var(--rounded-lg);
  padding: var(--space-3) var(--space-4);
  box-shadow: var(--shadow-sm);
}
.card h2 {
  margin: 0 0 var(--space-2);
  font: 600 var(--text-base) / 1.25 var(--font-sans);
}
.card.balance h2 { color: var(--tg-text); }

.balance-amount {
  font: 700 var(--text-2xl) / 1.2 var(--font-mono);
  font-variant-numeric: tabular-nums;
  margin: var(--space-1) 0;
  color: var(--tg-text);
}

/* Phase 78 (jun15) T5 Q5: copy-hint cue. Hidden by default, fades
   in on hover/focus to signal "this is interactive". The balance
   number itself is the click target. */
.balance-amount .copy-hint {
  display: inline-block;
  margin-left: var(--space-2);
  font-size: var(--text-sm);
  opacity: 0;
  transition: opacity 0.15s ease;
  vertical-align: middle;
}
.balance-amount:hover .copy-hint,
.balance-amount:focus-visible .copy-hint {
  opacity: 0.6;
}
.balance-amount:focus-visible {
  outline: 2px solid var(--tg-accent);
  outline-offset: 2px;
  border-radius: var(--rounded-sm);
}

.family-card-tma {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.muted { color: var(--tg-muted); font-size: var(--text-sm); }

button.btn {
  margin: var(--space-1) var(--space-2) 0 0;
}

#tx-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.tx-row {
  display: grid;
  grid-template-columns: 90px 1fr 80px 90px;
  gap: var(--space-1);
  align-items: center;
  font-size: var(--text-sm);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-surface-3);
}
.tx-row:last-child { border-bottom: none; }
.tx-date  { color: var(--tg-muted); font-variant-numeric: tabular-nums; }
.tx-desc  { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tx-cat   {
  color: var(--tg-muted);
  font-size: var(--text-xs);
  /* Phase 45 (jun13): long category names ("Дополнительные расходы")
     overflow the 80px grid column and break the alignment of the
     amount column. Ellipsize like .tx-desc. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tx-amount {
  text-align: right;
  font-family: var(--font-mono);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.amount-income  { color: var(--color-success); }
.amount-expense { color: var(--color-danger); }

/* Phase 46 (jun13): compact button variant for the inline
   "Обновить" button on the transactions card. */
.btn--small {
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-2);
  margin: 0 0 var(--space-2) 0;
}

/* Phase 47 (jun13): segmented period selector (День / Неделя / Месяц / Год)
   at the top of the transactions card. The active tab is highlighted with
   the Telegram accent color and a subtle inset shadow; inactive tabs are
   bordered but transparent. */
.tx-period-tabs {
  display: flex;
  gap: 2px;
  margin: 0 0 var(--space-2) 0;
  padding: 2px;
  background: var(--color-surface, rgba(0, 0, 0, 0.04));
  border-radius: var(--radius-sm, 6px);
  overflow: hidden;
}
.tx-period-tab {
  flex: 1;
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--color-text, inherit);
  font-family: inherit;
  font-size: var(--text-xs);
  font-weight: 500;
  padding: var(--space-1) var(--space-2);
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
  opacity: 0.7;
}
.tx-period-tab:hover { opacity: 1; }
.tx-period-tab.is-active {
  background: var(--tg-accent, #3390ec);
  color: #fff;
  opacity: 1;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.tx-period-tab:focus-visible {
  outline: 2px solid var(--tg-accent, #3390ec);
  outline-offset: 1px;
}

.card.error {
  background: rgba(239, 68, 68, 0.10);
  border: 1px solid rgba(239, 68, 68, 0.40);
}
.card.error h2 { color: var(--color-danger); }
.card.error .btn { margin-top: var(--space-3); width: 100%; }

/* Phase 56 (jun14): error-card uses position:fixed so it stays visible
 * even when the page is scrolled or content above pushes it off-screen.
 * On Telegram the error path is rare (invalid initData → HTTP 401 from
 * backend), but we still want users to see the retry button.
 *
 * Phase 73 (jun14, headless review): the background used to read
 *   `var(--bg, #1a1a1a)` — but `--bg` is a DEAD variable (the
 * Pitfall-1 cleanup renamed it to `--tg-bg` in Phase 61). Result:
 * error card was always dark gray regardless of Telegram theme.
 * Fix: use `--tg-bg` so the error card matches the user's theme,
 * with the dark fallback as a safety net. */
#error-card {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: var(--tg-bg, #1a1a1a);
  align-items: center;
  justify-content: center;
}
#error-card > .card { max-width: 360px; width: calc(100% - var(--space-5)); }

/* Phase 56 (jun14): CRITICAL — the `main.tma-main` rule above sets
 * `display: flex` with higher specificity than the built-in
 * `[hidden] { display: none }`. Without this !important the error card
 * (and any other `<main hidden>`) stays visible above the root after
 * successful auth, blocking all interaction. */
[hidden] { display: none !important; }

/* ============================================================================
 * Phase 46 (jun13): Quick-Add form (structured + one-line text modes).
 * Plain HTML form, no framework. Layout: stacked labels with 8px gap so the
 * Telegram WebView keyboard appears right under the focused field.
 * ============================================================================ */
.quick-add {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.qa-label {
  font-size: var(--text-xs);
  color: var(--tg-muted);
  margin-top: var(--space-1);
}
.qa-input {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-surface-3);
  border-radius: var(--rounded-md);
  background: var(--color-surface);
  color: var(--tg-text);
  font: 500 var(--text-base) / 1.4 var(--font-sans);
  box-sizing: border-box;
  /* Disable the iOS/Telegram autofill yellow tint */
  -webkit-text-fill-color: var(--tg-text);
  -webkit-box-shadow: 0 0 0 1000px var(--color-surface) inset;
}
.qa-input:focus {
  outline: 2px solid var(--tg-accent);
  outline-offset: -1px;
  border-color: var(--tg-accent);
}
.qa-mode-row {
  display: flex;
  gap: var(--space-1);
  background: var(--color-surface);
  padding: 2px;
  border-radius: var(--rounded-md);
  width: fit-content;
}
.qa-mode-tab {
  border: 0;
  background: transparent;
  color: var(--tg-muted);
  padding: var(--space-1) var(--space-3);
  border-radius: calc(var(--rounded-md) - 2px);
  font: 500 var(--text-sm) / 1.2 var(--font-sans);
  cursor: pointer;
}
.qa-mode-tab.is-active {
  background: var(--tg-accent);
  color: var(--tg-on-accent);
}
.qa-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-1);
}
.qa-actions .btn { margin: 0; flex: 1; }
.qa-status {
  min-height: 1.2em;
  margin: 0;
  font-size: var(--text-xs);
  transition: color 200ms ease;
}
.qa-status.is-ok  { color: var(--color-success); }
.qa-status.is-err { color: var(--color-danger); }
.qa-submitting #qa-submit {
  opacity: 0.6;
  pointer-events: none;
}

/* Phase 68 (jun14): offline-resilience banner. */
.offline-banner {
  position: sticky;
  top: 0;
  z-index: 100;
  padding: 12px 16px;
  background: var(--tg-accent, #f0a020);
  color: var(--tg-on-accent, #1a1a1a);
  font-size: 14px;
  font-weight: 500;
  text-align: center;
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
  animation: pulse-banner 2.4s ease-in-out infinite;
}

@keyframes pulse-banner {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.7; }
}

/* ============================================================================
 * Phase 77 (jun15): bottom dock, drawer, modal.
 *
 * Three new UI surfaces:
 *   1. `.tma-dock` — sticky bottom bar with [+ input ≡] row + two action
 *      buttons row below. Replaces the Telegram MainButton (which is
 *      hidden in tma.v52.js). Safe-area-aware.
 *   2. `.tma-drawer` — bottom sheet that slides up when ≡ is tapped.
 *      Used for navigation/commands.
 *   3. `.tma-modal` — centered modal for the balance breakdown.
 *
 * Touch targets: every button is ≥44px tall (iOS HIG).
 * ============================================================================ */

/* Phase 77: dock is `position: fixed; bottom: 0` so it stays visible
   while the user scrolls. The body gets padding-bottom equal to the
   dock's height so the last card isn't hidden behind it. The
   `env(safe-area-inset-bottom)` adds the iOS home-indicator offset. */
.tma-dock {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 200;
  padding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px));
  background: var(--tg-card);
  border-top: 1px solid color-mix(in srgb, var(--tg-text) 8%, transparent);
  box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.06);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.tma-dock-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
}

.tma-dock-btn {
  flex: 0 0 auto;
  width: 48px;
  height: 48px;
  border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--tg-text) 12%, transparent);
  background: transparent;
  color: var(--tg-text);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s, transform 0.1s;
  /* Phase 77: iOS Safari strips default button styling but keeps tap
     highlight; we want tap feedback. */
  -webkit-tap-highlight-color: color-mix(in srgb, var(--tg-accent) 25%, transparent);
}

.tma-dock-btn:active {
  transform: scale(0.94);
}

.tma-dock-btn--primary {
  background: var(--tg-accent);
  color: var(--tg-on-accent);
  border-color: transparent;
  font-weight: 600;
  box-shadow: 0 2px 8px color-mix(in srgb, var(--tg-accent) 35%, transparent);
}

.tma-dock-input {
  flex: 1 1 auto;
  min-width: 0;  /* allow shrink inside flex */
  height: 48px;
  padding: 0 14px;
  border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--tg-text) 14%, transparent);
  background: color-mix(in srgb, var(--tg-text) 4%, transparent);
  color: var(--tg-text);
  font-family: inherit;
  font-size: 16px;  /* iOS won't auto-zoom on focus if ≥16px */
  outline: none;
  transition: border-color 0.15s, background 0.15s;
}

.tma-dock-input:focus {
  border-color: var(--tg-accent);
  background: var(--tg-bg);
}

.tma-dock-input::placeholder {
  color: var(--tg-muted);
}

.tma-dock-actions {
  display: flex;
  gap: 8px;
}

.tma-dock-action {
  flex: 1 1 50%;
  height: 40px;
  border-radius: 10px;
  border: 1px solid color-mix(in srgb, var(--tg-text) 12%, transparent);
  background: transparent;
  color: var(--tg-text);
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  -webkit-tap-highlight-color: color-mix(in srgb, var(--tg-accent) 25%, transparent);
  transition: background 0.15s, transform 0.1s;
}

.tma-dock-action:active {
  transform: scale(0.97);
}

/* Phase 77: body padding-bottom to make room for the dock. The
   96px covers the dock's natural height (48 input + 8 gap + 40 actions
   + 8+8 padding) and the safe-area inset. We don't use a CSS var
   here because the dock can be hidden in error/loading states. */
body.has-dock {
  padding-bottom: calc(96px + env(safe-area-inset-bottom, 0px));
}

/* Phase 77: drawer (bottom sheet menu). Hidden by default; the
   `[data-open]` attribute is set by tma.v52.js when ≡ is tapped.
   `aria-hidden=true` keeps it out of the screen reader tree. */
.tma-drawer {
  position: fixed;
  inset: 0;
  z-index: 300;
  pointer-events: none;
  visibility: hidden;
  transition: visibility 0s linear 0.2s;
}

.tma-drawer[aria-hidden="false"] {
  pointer-events: auto;
  visibility: visible;
  transition: visibility 0s linear 0s;
}

.tma-drawer-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity 0.2s;
}

.tma-drawer[aria-hidden="false"] .tma-drawer-backdrop {
  opacity: 1;
}

.tma-drawer-panel {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--tg-card);
  border-top-left-radius: 20px;
  border-top-right-radius: 20px;
  padding: 16px 16px calc(24px + env(safe-area-inset-bottom, 0px));
  transform: translateY(100%);
  transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
  max-height: 80vh;
  overflow-y: auto;
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.18);
}

.tma-drawer[aria-hidden="false"] .tma-drawer-panel {
  transform: translateY(0);
}

.tma-drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}

.tma-drawer-header h2 {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
}

.tma-drawer-close,
.tma-modal-close {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: none;
  background: color-mix(in srgb, var(--tg-text) 8%, transparent);
  color: var(--tg-text);
  font-size: 18px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  -webkit-tap-highlight-color: transparent;
}

.tma-drawer-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.tma-drawer-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 14px 16px;
  border: none;
  background: transparent;
  color: var(--tg-text);
  font-family: inherit;
  font-size: 16px;
  font-weight: 500;
  border-radius: 12px;
  cursor: pointer;
  -webkit-tap-highlight-color: color-mix(in srgb, var(--tg-accent) 25%, transparent);
  transition: background 0.1s;
}

.tma-drawer-item:active {
  background: color-mix(in srgb, var(--tg-text) 8%, transparent);
}

.tma-drawer-footer {
  text-align: center;
  margin-top: 12px;
  font-size: 12px;
}

/* Phase 77: modal (centered). Same visibility pattern as the drawer. */
.tma-modal {
  position: fixed;
  inset: 0;
  z-index: 400;
  pointer-events: none;
  visibility: hidden;
  transition: visibility 0s linear 0.2s;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.tma-modal[aria-hidden="false"] {
  pointer-events: auto;
  visibility: visible;
  transition: visibility 0s linear 0s;
}

.tma-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  opacity: 0;
  transition: opacity 0.2s;
}

.tma-modal[aria-hidden="false"] .tma-modal-backdrop {
  opacity: 1;
}

.tma-modal-panel {
  position: relative;
  background: var(--tg-card);
  border-radius: 20px;
  padding: 20px;
  max-width: 360px;
  width: 100%;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.25);
  transform: scale(0.92);
  opacity: 0;
  transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.2s;
}

.tma-modal[aria-hidden="false"] .tma-modal-panel {
  transform: scale(1);
  opacity: 1;
}

.tma-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}

.tma-modal-header h2 {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
}

.tma-modal-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.tma-modal-label {
  margin: 0;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--tg-muted);
}

.tma-modal-amount {
  margin: 0 0 12px 0;
  font-size: 32px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--tg-text);
}

.tma-modal-secondary {
  margin: 0 0 8px 0;
  font-size: 16px;
  font-weight: 500;
  color: var(--tg-text);
}

.tma-modal-bar-wrap {
  height: 12px;
  background: color-mix(in srgb, var(--tg-text) 8%, transparent);
  border-radius: 6px;
  overflow: hidden;
  margin: 8px 0 4px 0;
}

.tma-modal-bar {
  height: 100%;
  width: 0%;
  background: var(--tg-accent);
  border-radius: 6px;
  transition: width 0.4s cubic-bezier(0.16, 1, 0.3, 1),
              background-color 0.3s;
}

.tma-modal-bar.is-over {
  /* Phase 77: when utilization > 100%, the bar turns red so the user
     notices they've blown the budget. Otherwise it stays in the
     accent color. */
  background: #dc2626;
}

/* --- Phase 94 (jun17): TMA i18n-фикация + skeleton/popover/form-error ---

   Three new visual elements ship with the i18n refactor:

   1. `.tma-skeleton-row` / `.tma-skeleton-line` — placeholder rows
      shown in #tx-list while /api/v1/transactions is in flight.
      Replaces the previous "loading whole page" state which made
      period-switching feel slow. Animation is a horizontal shimmer
      (the standard Material-style "wave" of light moving left→right).
      The .tma-skeleton-row is `aria-hidden="true"` so screen readers
      skip it; the real <li> rows that follow carry the actual data.

   2. `.tma-popover` / `.tma-popover-*` — bottom-sheet popover for
      short explanations (Phase 94 sub-deliverable D: "insights coming
      soon"). Same interaction model as the existing .tma-drawer
      (backdrop click + close button + Escape key) but visually
      distinct — narrower, no drag handle, single body paragraph
      instead of an item list. Reuses the .tma-drawer animation
      timing (180ms ease-out) so the user gets a consistent feel.

   3. `.form-error` — small red text under an invalid field. Shown
      by tma.js::showInlineFormError() during client-side validation.
      The element starts with `hidden`, so absence of validation
      errors leaves the form looking clean. The id suffix on each
      instance (qa-amount-error, qa-desc-error, qa-cat-error,
      qa-text-error) lets the JS toggle them individually. */

.tma-skeleton-row {
  display: flex;
  gap: var(--space-2, 0.5rem);
  padding: var(--space-2, 0.5rem) 0;
  list-style: none;
}

.tma-skeleton-line {
  display: block;
  height: 0.9rem;
  border-radius: 0.25rem;
  background: linear-gradient(
    90deg,
    rgba(127, 127, 127, 0.08) 0%,
    rgba(127, 127, 127, 0.18) 50%,
    rgba(127, 127, 127, 0.08) 100%
  );
  background-size: 200% 100%;
  animation: tma-skeleton-shimmer 1.4s ease-in-out infinite;
}

.tma-skeleton-line--amount { width: 22%; }
.tma-skeleton-line--desc   { width: 38%; }
.tma-skeleton-line--meta   { width: 18%; }

@keyframes tma-skeleton-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .tma-skeleton-line {
    animation: none;
    background: rgba(127, 127, 127, 0.12);
  }
}

.tma-popover {
  position: fixed;
  inset: 0;
  z-index: 60; /* above .tma-drawer (50) and .tma-modal (55) */
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.tma-popover[hidden] { display: none; }

.tma-popover-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(15, 23, 42, 0.55);
  animation: tma-popover-fade-in 180ms ease-out;
}

.tma-popover-panel {
  position: relative;
  width: 100%;
  max-width: 28rem;
  margin: 0 var(--space-3, 0.75rem) var(--space-3, 0.75rem);
  background: var(--card, #1e293b);
  color: var(--text, #f1f5f9);
  border-radius: 1rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  padding: var(--space-4, 1rem) var(--space-4, 1rem) var(--space-3, 0.75rem);
  animation: tma-popover-slide-up 180ms ease-out;
}

.tma-popover-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2, 0.5rem);
  margin-bottom: var(--space-2, 0.5rem);
}

.tma-popover-header h2 {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 600;
}

.tma-popover-close {
  background: transparent;
  border: 0;
  color: var(--muted, #94a3b8);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.25rem 0.5rem;
}

.tma-popover-body {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.45;
  color: var(--muted, #cbd5e1);
}

@keyframes tma-popover-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes tma-popover-slide-up {
  from { transform: translateY(20px); opacity: 0; }
  to   { transform: translateY(0);     opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .tma-popover-backdrop,
  .tma-popover-panel {
    animation: none;
  }
}

/* Inline form errors (Phase 94 sub-deliverable C). Small red text
   under the offending field. The <small> tag is hidden by default
   (`hidden` attribute); tma.js removes the attribute when the
   validation rule fails. The error color follows the project's
   --error CSS var (set in tokens.css) and falls back to #dc2626
   if the var is missing (e.g. when the page is opened in a context
   that doesn't load tokens.css). */
.form-error {
  display: block;
  margin-top: 0.25rem;
  color: var(--error, #dc2626);
  font-size: 0.8125rem;
  line-height: 1.3;
}

.form-error[hidden] { display: none; }

/* Highlight the offending input itself so the user knows where
   to look. The class is toggled by tma.js alongside the .form-error
   <small>. */
.qa-input.has-error {
  border-color: var(--error, #dc2626);
  box-shadow: 0 0 0 1px var(--error, #dc2626);
}
