/* ==========================================================================
   Forms — one component set for every form on the site.

   Used by the public search, both sign-in screens, the admin dashboard, and
   the partner listing editor. Markup comes from src/lib/forms.js so the class
   names below are the only ones any form ever needs.

   Depends on tokens.css.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Layout
   -------------------------------------------------------------------------- */

.form {
  display: grid;
  gap: 1rem;
}

/* Two columns once there is room, with .field--wide spanning both. */
.form--split {
  grid-template-columns: 1fr;
}

@media (min-width: 44rem) {
  .form--split {
    grid-template-columns: 1fr 1fr;
  }

  .form--split .field--wide,
  .form--split .form__actions,
  .form--split .form__note {
    grid-column: 1 / -1;
  }
}

/* A row of controls that should sit side by side and wrap when they cannot. */
.form--inline {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
}

.form--inline .field {
  flex: 1 1 11rem;
  min-width: 0;
}

.form__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
}

.form__note {
  font-size: 0.85rem;
  color: var(--ink-soft);
}

fieldset.form__group {
  margin: 0;
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

fieldset.form__group > legend {
  padding-inline: 0.4rem;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* --------------------------------------------------------------------------
   A field
   -------------------------------------------------------------------------- */

.field {
  display: grid;
  gap: 0.3rem;
  min-width: 0;
}

.field__label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink);
}

/* Marks a required control without relying on colour alone. */
.field__required {
  color: var(--brand);
  font-weight: 700;
  margin-left: 0.15rem;
}

.field__hint,
/* Some labels carry their hint inline as <small>; it still belongs on its own
   line under the label rather than running on after it. */
.field__label small {
  display: block;
  font-size: 0.8rem;
  font-weight: 400;
  line-height: 1.35;
  color: var(--ink-faint);
}

/* --------------------------------------------------------------------------
   Controls
   -------------------------------------------------------------------------- */

.input,
.select,
.textarea {
  width: 100%;
  min-width: 0;
  min-height: var(--tap);
  padding: 0.55rem 0.7rem;
  font-family: inherit;
  /* 16px keeps iOS from zooming the page when a field takes focus. */
  font-size: 1rem;
  line-height: 1.4;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  transition:
    border-color 0.15s ease,
    box-shadow 0.15s ease;
}

@media (min-width: 44rem) {
  .input,
  .select,
  .textarea {
    font-size: 0.94rem;
  }
}

.input::placeholder,
.textarea::placeholder {
  color: var(--ink-faint);
}

.input:hover,
.select:hover,
.textarea:hover {
  border-color: var(--ink-faint);
}

.input:focus,
.select:focus,
.textarea:focus {
  border-color: var(--action-site);
  box-shadow: 0 0 0 3px rgb(13 92 171 / 15%);
  outline: none;
}

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

.select {
  appearance: none;
  /* Room for the chevron drawn by the wrapper. */
  padding-right: 2.2rem;
}

.field__select {
  position: relative;
  display: grid;
}

.field__select::after {
  content: '';
  position: absolute;
  right: 0.9rem;
  top: 50%;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid var(--ink-soft);
  border-bottom: 2px solid var(--ink-soft);
  transform: translateY(-70%) rotate(45deg);
  pointer-events: none;
}

.input[type='color'] {
  padding: 0.25rem;
  width: 3.5rem;
  min-height: 2.6rem;
  cursor: pointer;
}

.input[type='range'] {
  padding: 0;
  min-height: auto;
  border: 0;
  background: none;
  accent-color: var(--brand);
}

.input[type='file'] {
  padding: 0.4rem;
  cursor: pointer;
}

.input[type='file']::file-selector-button {
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  margin-right: 0.6rem;
  padding: 0.35rem 0.7rem;
  color: var(--ink);
  background: var(--surface-sunken);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-xs);
  cursor: pointer;
}

.input:disabled,
.select:disabled,
.textarea:disabled {
  background: var(--surface-sunken);
  color: var(--ink-faint);
  cursor: not-allowed;
}

.input[readonly] {
  background: var(--surface-sunken);
}

/* --------------------------------------------------------------------------
   Validation state

   just-validate is configured with these class names, so the same styling
   covers both its client-side messages and the server's field errors.
   -------------------------------------------------------------------------- */

.input.is-invalid,
.select.is-invalid,
.textarea.is-invalid {
  border-color: var(--danger);
  background: var(--danger-soft);
}

.input.is-invalid:focus,
.select.is-invalid:focus,
.textarea.is-invalid:focus {
  box-shadow: 0 0 0 3px rgb(200 16 46 / 18%);
}

.input.is-valid,
.select.is-valid,
.textarea.is-valid {
  border-color: var(--ok);
}

.field__error {
  font-size: 0.8rem;
  font-weight: 600;
  font-style: normal;
  color: var(--danger);
}

/* Collapses when there is nothing to say, so the form does not jump. */
.field__error:empty {
  display: none;
}

/* --------------------------------------------------------------------------
   Form-level message
   -------------------------------------------------------------------------- */

.form__status {
  min-height: 1.25rem;
  margin: 0;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

.form__status.is-ok {
  color: var(--ok);
  font-weight: 600;
}

.form__status.is-error {
  color: var(--danger);
  font-weight: 600;
}

.form__status.is-busy {
  color: var(--ink-faint);
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.btn-form {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-height: var(--tap);
  padding: 0.5rem 1.15rem;
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 600;
  white-space: nowrap;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease,
    box-shadow 0.15s ease,
    transform 0.15s ease;
}

.btn-form--primary {
  color: var(--action-ink);
  background: var(--action-site);
  border-color: var(--action-site);
}

.btn-form--brand {
  color: var(--brand-ink);
  background: var(--brand);
  border-color: var(--brand);
  box-shadow: var(--shadow-brand);
}

.btn-form--danger {
  color: var(--danger);
  border-color: var(--danger);
}

.btn-form--quiet {
  border-color: transparent;
  color: var(--ink-soft);
}

.btn-form--small {
  min-height: 2.25rem;
  padding: 0.3rem 0.7rem;
  font-size: 0.8rem;
}

/* The lift and the press come from the shared layer below; these are the
   colours each variant moves to. A filter would wash the solid ones toward
   grey, so every one of them names its hover shade. */
@media (hover: hover) {
  .btn-form--primary:hover {
    background: var(--action-site-hover);
    border-color: var(--action-site-hover);
  }

  .btn-form--brand:hover {
    background: var(--brand-bright);
    border-color: var(--brand-bright);
  }

  .btn-form--danger:hover {
    background: var(--danger-soft);
    border-color: var(--danger);
  }

  .btn-form--quiet:hover {
    color: var(--ink);
    border-color: var(--line-strong);
  }
}

.btn-form:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

/* --------------------------------------------------------------------------
   Button feel — one interaction model for every button on the site

   Public cards, filter chips, both sign-in screens, the dashboard and the
   partner portal all answer the pointer the same way: lift a little under it,
   settle when pressed, and move between states rather than snapping.

   Everything here is wrapped in :where(), which holds it at zero specificity.
   A component's own colours therefore always win -- this layer contributes the
   behaviour and nothing else, so nobody has to fight it with !important.

   Two guards worth keeping: the lift only exists on pointer devices, because a
   phone leaves :hover stuck on the last thing tapped; and it collapses under
   prefers-reduced-motion, where the colour change alone carries the state.
   -------------------------------------------------------------------------- */

:where(
  button,
  .btn,
  .btn-form,
  .btn-small,
  .chip,
  .bar__link,
  .export,
  .need__clear,
  .footer__login,
  [role='button']
) {
  cursor: pointer;
  transition:
    background-color 0.16s ease,
    border-color 0.16s ease,
    color 0.16s ease,
    box-shadow 0.16s ease,
    filter 0.16s ease,
    transform 0.12s ease;
}

/*
 * Rows and close crosses are excluded from the lift. An accordion header that
 * floats off its own list, or an X that climbs out of the corner it sits in,
 * reads as a glitch rather than as a response.
 */
@media (hover: hover) {
  :where(
    button,
    .btn,
    .btn-form,
    .btn-small,
    .chip,
    .bar__link,
    .export,
    .need__clear,
    .footer__login,
    [role='button']
  ):hover:not(
      :disabled,
      [aria-disabled='true'],
      .offer__toggle,
      .offers-modal__close,
      .drawer-close,
      .tabs__tab
    ) {
    box-shadow: var(--shadow-lift);
    transform: translateY(-1px);
  }

  /* Neutral buttons -- the ones that are just a surface and a hairline -- warm
     up and firm their edge, so the change is visible without colour. */
  :where(button, .btn-form, .btn-small, .bar__link, .export, .need__clear):hover:not(
      :disabled,
      [aria-disabled='true'],
      .btn-form--primary,
      .btn-form--brand,
      .offer__toggle,
      .offers-modal__close,
      .drawer-close,
      .tabs__tab
    ) {
    background-color: var(--surface-hover);
    border-color: var(--line-strong);
  }
}

/* Pressed: back to the surface it lifted from, a touch smaller. This is the
   half of the interaction that makes a button feel like a button. */
:where(
  button,
  .btn,
  .btn-form,
  .btn-small,
  .chip,
  .bar__link,
  .export,
  .need__clear,
  .footer__login,
  [role='button']
):active:not(:disabled, [aria-disabled='true']) {
  transform: translateY(0) scale(0.985);
  box-shadow: none;
  transition-duration: 0.06s;
}

:where(button, .btn, .btn-form, .btn-small, .chip, [role='button']):disabled {
  cursor: not-allowed;
  opacity: 0.55;
  transform: none;
  box-shadow: none;
}

@media (prefers-reduced-motion: reduce) {
  :where(
    button,
    .btn,
    .btn-form,
    .btn-small,
    .chip,
    .bar__link,
    .export,
    .need__clear,
    .footer__login,
    [role='button']
  ):hover,
  :where(
    button,
    .btn,
    .btn-form,
    .btn-small,
    .chip,
    .bar__link,
    .export,
    .need__clear,
    .footer__login,
    [role='button']
  ):active {
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   Character counter
   -------------------------------------------------------------------------- */

.field__count {
  justify-self: end;
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}

.field__count.is-near {
  color: var(--warn);
  font-weight: 600;
}

.field__count.is-over {
  color: var(--danger);
  font-weight: 700;
}
