/* ============================================================================
   FORMS — shared by the marketing site and the app, so the contact form and
   the login/billing forms are the same object. Tokens only.

   The live site labels its fields with placeholders alone. Placeholders vanish
   the moment someone types, which leaves anyone using a screen reader — or
   anyone who looked away mid-form — with an unlabelled box. Every field here
   keeps a real <label>: visually hidden while the field is empty, so the design
   is unchanged, and revealed as a small caption once it has content.
   ========================================================================== */

.form { display: grid; gap: var(--space-4); }

.field { display: grid; gap: var(--space-1); }

.field__label {
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
  /* Collapsed, not hidden: it takes no space until the field is filled, and
     assistive tech reads it either way. */
  height: 0;
  overflow: hidden;
  opacity: 0;
  transition: height var(--transition), opacity var(--transition);
}
.field__control:not(:placeholder-shown) + .field__label,
.field__control:focus + .field__label {
  height: 1.2em;
  opacity: 1;
}

/* The control comes first in the DOM so the sibling selector above can reach
   the label; `order` puts the label back on top visually. */
.field__control { order: -2; }
.field__label   { order: -1; }

.field__control {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  color: var(--color-heading);
  background: transparent;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius);
  transition: border-color var(--transition), box-shadow var(--transition);
}
.field__control::placeholder { color: var(--color-text-muted); opacity: 1; }

.field__control:hover { border-color: var(--color-heading); }

.field__control:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--glow-accent);
}

textarea.field__control { min-height: 8rem; resize: vertical; }

/* ---- Per-field error ---------------------------------------------------- */
.field__control[aria-invalid='true'] { border-color: var(--color-error); }
.field__control[aria-invalid='true']:focus {
  box-shadow: 0 0 0 1px var(--color-error), 0 0 20px rgba(255, 93, 93, 0.25);
}

.field__error {
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  color: var(--color-error);
}
.field__error:empty { display: none; }

/* ---- Honeypot ------------------------------------------------------------
   Off-screen rather than display:none — some bots skip anything explicitly
   hidden. tabindex=-1 and aria-hidden keep it away from real people. */
.form__trap {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  overflow: hidden;
}

/* ---- Submit button, with its loading state ------------------------------ */
.form__submit { width: 100%; position: relative; }

.form__submit[aria-busy='true'] { pointer-events: none; }
/* The label goes, the spinner arrives, and the button keeps its exact size —
   nothing under it jumps while the message is in flight. */
.form__submit[aria-busy='true'] .form__submit-label { visibility: hidden; }

/* The label wraps both the text and its icon, so it has to lay them out the
   way .btn would have — otherwise the icon drops onto its own line. */
.form__submit-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  white-space: nowrap;
}
.form__submit-label svg { flex: none; }

.form__spinner {
  position: absolute; inset: 0;
  display: none;
  place-items: center;
}
.form__submit[aria-busy='true'] .form__spinner { display: grid; }

.form__spinner::before {
  content: '';
  width: 1.25em; height: 1.25em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: var(--radius-pill);
  animation: form-spin 700ms linear infinite;
}

@keyframes form-spin { to { rotate: 360deg; } }

@media (prefers-reduced-motion: reduce) {
  /* A pulse still says "working" without spinning anything. */
  .form__spinner::before { animation: form-pulse 1.4s ease-in-out infinite; }
  @keyframes form-pulse { 50% { opacity: 0.25; } }
}

/* ---- The one status line under the form --------------------------------- */
.form__status {
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  border: 1px solid transparent;
}
.form__status:empty { display: none; }

.form__status[data-state='success'] {
  color: var(--color-success);
  border-color: var(--color-success);
  background: rgba(51, 196, 129, 0.08);
}
.form__status[data-state='error'] {
  color: var(--color-error);
  border-color: var(--color-error);
  background: rgba(255, 93, 93, 0.08);
}
