/* Form field: label above, help under the label, error under the control.
   Never a placeholder as a label. DESIGN.md section 4 (Inputs). */

@layer components {
  /* Two columns, and only the label row uses the second one.

     Every field rendered an always-present status paragraph under its control
     with `min-height: 1.4em` reserved, so every field in the product carried
     roughly 28px of empty space (the line plus the row gap) for a feature one
     field in the whole app uses. On the sign-up form that was 112px; on an
     eight-field component form it was over 200px. `component-form.css` had already
     patched it locally with `.field__status:empty { display: none }`, which
     fixes the waste and buys a jump the moment the status fills.

     Putting the status at the end of the label's row costs nothing when it is
     empty, shifts nothing when it fills, and puts availability feedback where
     a person is already looking. */
  .field {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--space-2);
  }

  /* Everything spans both tracks unless it is one of the two label-row items
     below. Declared first on purpose: the two rules that follow are the same
     specificity and win on source order. */
  .field > * { grid-column: 1 / -1; }

  /* THE MONO SHAPE, BUT NOT THE FAINT INK AND NOT THE CAPS. A field label
     takes the same face and tracking as the product's section labels so a
     form looks like the rest of the page - but NOT `kit.css`'s `.label`, and
     neither of the two things that class does to make a mark recede.

     `--text-faint` is out because `DESIGN.md` reserves that tier for marks a
     reader never has to act on, and a field label is the opposite: it is the
     word you read to know what to type.

     UPPERCASE IS OUT FOR THE SAME REASON, and the test suite made the case
     better than the argument did. `toHaveText` compares RENDERED text, so
     CSS caps turned "Name, Summary, Body" into "NAME, SUMMARY, BODY" and
     `worldbuilding.spec.js` failed on the form it guards. A spec can be
     updated; what it was really reporting is that the label a reader scans to
     fill in a field had been made harder to scan, which is not a trade this
     page needed to make for the sake of matching a section heading. */
  .field__label {
    font-family: var(--font-mono);
    letter-spacing: 0.08em;
    grid-column: 1;
    grid-row: 1;
    font-size: var(--text-sm);
    line-height: var(--text-sm-lh);
    font-weight: 500;
    color: var(--text);
  }

  .field__label[data-optional]::after {
    content: " optional";
    color: var(--text-muted);
    font-weight: 400;
  }

  .field__help {
    font-size: var(--text-sm);
    line-height: var(--text-sm-lh);
    color: var(--text-muted);
    margin-top: calc(var(--space-1) * -1);
  }

  /* ── Longhands, not shorthands, and this is load-bearing ──────────────
     `background` and `padding` here are the base for every control in the
     product, and a shorthand resets every longhand it covers. `ui/select`'s
     `.nv-select` sets `background-image` for its caret and `padding-inline-end`
     for the room the caret needs, at the same specificity and in the same
     layer, so which of the two won came down to which stylesheet `ensureCss`
     had appended last: a select rendered with its caret on a route where a
     text field mounted first, and without one where a select did.

     Measured on the shipped rules, same markup, same page:

       field.css then select.css   caret present   padding-inline-end 48px
       select.css then field.css   caret MISSING   padding-inline-end 12px

     `background-color` cannot reset a variant's `background-image`, which
     removes that half of the collision for every future variant rather than
     only for this one. The padding half cannot be solved here, because a
     longhand against the same longhand is a genuine tie: `select.css` raises
     its own specificity instead, and says so. */
  .field__control {
    width: 100%;
    min-height: calc(44px * var(--ui-scale));
    padding-block: var(--space-2);
    padding-inline-start: var(--space-3);
    padding-inline-end: var(--space-3);
    background-color: var(--surface-2);
    backdrop-filter: var(--glass-filter);
    border: 1px solid var(--border);
    /* SQUARE, with every other control (2026-10-05). A field was the last
       rounded box on a page whose buttons, cards, tabs and panels had all
       gone square. */
    border-radius: 0;
    color: var(--text);
    font-size: var(--text-base);   /* 16px minimum: stops iOS zooming on focus */
    line-height: var(--text-base-lh);
    transition: border-color var(--dur-2) var(--ease-out),
                background-color var(--dur-2) var(--ease-out);
  }

  /* The multiline branch of field() (bl-16). The base rule gives it width,
     surface, border and type; this only gives it room to hold a few lines
     and lets it be pulled taller, never wider. */
  textarea.field__control {
    min-height: calc(88px * var(--ui-scale));
    resize: vertical;
    max-inline-size: 100%;
  }

  .field__control::placeholder {
    color: var(--text-muted);
    opacity: 1;                    /* Firefox dims placeholders by default */
  }

  .field__control:hover:not(:disabled) {
    border-color: color-mix(in oklab, var(--border) 60%, var(--text));
  }

  .field__control:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
    border-color: var(--accent);
  }

  .field__control:disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }

  .field[data-invalid] .field__control {
    border-color: var(--danger);
  }

  .field__error {
    display: flex;
    align-items: start;
    gap: var(--space-2);
    font-size: var(--text-sm);
    line-height: var(--text-sm-lh);
    color: var(--danger);
  }

  /* Not colour alone: an icon carries the same signal. */
  .field__error::before {
    content: "!";
    flex: 0 0 auto;
    width: 1.15em;
    height: 1.15em;
    border: 1px solid currentColor;
    border-radius: 50%;
    font-size: 0.8em;
    font-weight: 700;
    display: grid;
    place-items: center;
    margin-top: 0.15em;
  }

  .field__status {
    grid-column: 2;
    grid-row: 1;
    justify-self: end;
    text-align: end;
    font-size: var(--text-sm);
    line-height: var(--text-sm-lh);
    color: var(--text-muted);
  }
  .field__status[data-state="ok"] { color: var(--success); }
  .field__status[data-state="bad"] { color: var(--danger); }

  /* A whole-form error, above the submit button. */
  .form-error {
    display: flex;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--danger);
    border-radius: 0;
    background: color-mix(in oklab, var(--danger) 12%, transparent);
    color: var(--text);
    font-size: var(--text-sm);
    line-height: var(--text-sm-lh);
  }
}
