/*
 * THE FIELD'S VERTICAL RHYTHM: the label binds to its control a `6` below it — the 4–8 the common
 * form systems use — and everything the field says under the control stands `8` down, so every field
 * in a grid has the same block above its control. The trailing `16` separates fields, more than twice
 * the label's gap, so a label never reads as the field above's; a grid states only its column gutter.
 * `control-first` re-places the same five parts.
 */
@layer theme, base, lotics, components, utilities;
@layer lotics.tokens, lotics.reset, lotics.components;

@layer lotics.components {
  .lotics-form-field {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    min-width: 0;
    padding-bottom: var(--lotics-space-16);
  }

  /* WHERE A LINK LANDED (`CalloutLink`): the field — its label and its value — ringed in the accent a moment, as an
     act is (`act_group.css`), a rung off its words and clear of its trailing air, fading once the link lets it go. */
  .lotics-form-field::after {
    content: "";
    position: absolute;
    /* A rung out on three sides, and short of the 16 trailing air by that rung below. */
    inset: calc(var(--lotics-space-4) * -1) calc(var(--lotics-space-4) * -1) var(--lotics-space-12);
    border-radius: var(--lotics-radius-control);
    box-shadow: 0 0 0 var(--lotics-ring-width) transparent;
    pointer-events: none;
    transition: box-shadow var(--lotics-duration-slow) var(--lotics-ease-standard);
  }

  .lotics-form-field[data-landed]::after {
    box-shadow: 0 0 0 var(--lotics-ring-width) var(--lotics-accent);
  }

  /* `1 / -1` reaches the last grid line, even an `auto-fill` count; inert outside a grid. */
  .lotics-form-field[data-span] {
    grid-column: 1 / -1;
  }

  /* One line, the mark right after the label's words: a label that wrapped set its control lower than the
     one beside it in a pair, and a mark pushed to the column's far end read as a stray beside a narrow
     control. The mark is a word of its own, set apart by the gap and its quiet ink, never joined by a `·`. */
  .lotics-form-field__header {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    min-width: 0;
    gap: var(--lotics-space-8);
  }

  .lotics-form-field__label {
    flex-shrink: 1;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-family: var(--font-sans);
    font-size: var(--lotics-text-sm);
    line-height: var(--lotics-leading-tight-sm);
    letter-spacing: var(--lotics-tracking-sm);
    font-weight: var(--lotics-weight-medium);
    color: var(--lotics-ink-default);
    /* A field sets a value; the pointer is for what acts. */
    cursor: default;
  }

  /* The label yields; the marker keeps one line. */
  .lotics-form-field__mark {
    flex-shrink: 0;
    font-family: var(--font-sans);
    font-size: var(--lotics-text-sm);
    line-height: var(--lotics-leading-tight-sm);
    letter-spacing: var(--lotics-tracking-sm);
    font-weight: var(--lotics-weight-regular);
    color: var(--lotics-ink-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* THE VALUE'S OWN ACTS (`FormFieldActs`) stand at a line's end: the label's, where the label stands over the
     value, holding nothing when the value states none; else the value's own first line, level with a label
     beside it. */
  .lotics-form-field__acts {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: flex-end;
    gap: var(--lotics-space-8);
    min-width: 0;
  }

  .lotics-form-field__acts[data-at="label"] {
    flex-shrink: 0;
    margin-inline-start: auto;
  }

  .lotics-form-field__acts[data-at="label"]:empty {
    display: none;
  }

  .lotics-form-field__description,
  .lotics-form-field__warning,
  .lotics-form-field__error {
    font-family: var(--font-sans);
    font-size: var(--lotics-text-sm);
    line-height: var(--lotics-leading-sm);
    letter-spacing: var(--lotics-tracking-sm);
    font-weight: var(--lotics-weight-regular);
    margin: 0;
  }

  .lotics-form-field__description {
    min-width: 0;
    margin-top: var(--lotics-space-8);
    color: var(--lotics-ink-muted);
  }

  .lotics-form-field__warning {
    margin-top: var(--lotics-space-8);
    color: var(--lotics-ink-warning);
  }

  .lotics-form-field__error {
    margin-top: var(--lotics-space-8);
    color: var(--lotics-ink-danger);
  }

  /* The caller's control, in a flex column with stretched children. Its value is one type whatever the field: an
     option's word as heavy as a typed one's. */
  .lotics-form-field__control {
    --lotics-status-weight: var(--lotics-input-weight);
    display: flex;
    flex-direction: column;
    align-items: stretch;
    min-width: 0;
    margin-top: var(--lotics-space-6);
  }

  /* Stacked, the body stands at a control's height, what is shorter (a switch, a value read) centred on the
     line a control's words sit on, so a row of fields reads as one line. Taller content grows down. */
  .lotics-form-field__control[data-layout="stacked"] {
    justify-content: center;
    min-height: var(--lotics-control-height);
  }

  /* A value read stands flush left, at its own width. */
  .lotics-form-field__control[data-read] {
    align-items: flex-start;
  }

  /* Label beside: a column of labels, each wrapping rather than cut; what the field says stands under the
     control. */
  /* Each row is what it holds: the label's rows under the control are empty but for what it says, so no spare
     height lands in them. */
  .lotics-form-field[data-layout="beside"] {
    display: grid;
    grid-template-columns: var(--lotics-control-min-width) minmax(0, 1fr);
    grid-auto-rows: max-content;
    column-gap: var(--lotics-space-16);
  }

  .lotics-form-field__control[data-layout="beside"],
  .lotics-form-field__description[data-layout="beside"],
  .lotics-form-field__warning[data-layout="beside"],
  .lotics-form-field__error[data-layout="beside"] {
    grid-column: 2;
  }

  /* Its first line stands on its value's first line, however many lines it wraps to: centred on where the value's
     first line centres (`--lotics-form-field-line`, read off the value as drawn — a file row's title under its row's
     air, a control's words), and until that is read on the line a control's words stand on. A mark stands under the
     label rather than take the narrow column's width from it. */
  .lotics-form-field__header[data-layout="beside"] {
    grid-column: 1;
    grid-row: 1 / span 4;
    align-self: start;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    --lotics-form-field-rest: calc(var(--lotics-control-height) / 2);
    padding-top: calc(var(--lotics-form-field-line, var(--lotics-form-field-rest)) - var(--lotics-leading-tight-sm) / 2);
  }

  .lotics-form-field__label[data-layout="beside"] {
    white-space: normal;
    font-weight: var(--lotics-weight-regular);
    color: var(--lotics-ink-muted);
  }

  .lotics-form-field__control[data-layout="beside"] {
    justify-content: center;
    min-height: var(--lotics-control-height);
    margin-top: 0;
  }

  /* A short value's control stands at its measure from the label column, never across the reading column;
     what it says under it (a countdown) keeps to the same edge. */
  .lotics-form-field__control[data-layout="beside"][data-measure="short"] {
    width: 100%;
    max-width: var(--lotics-control-short-width);
  }

  /* A linked row read over its line hangs from a control's content inset, as the picker's chosen row does:
     its title on the line a control's words stand on, the line under it. After the layouts, which
     centre a read. */
  .lotics-form-field__control[data-read="row"] {
    justify-content: flex-start;
    padding-top: calc((var(--lotics-control-height) - var(--lotics-control-content-height)) / 2);
  }

  /* A read among reads rests as text: the row at its words' pitch, never a control's height it does not have,
     and a label beside it on its words' line. After the layouts and the row read, which seat a control's. */
  .lotics-form-field__control[data-read][data-pitch="text"] {
    min-height: var(--lotics-leading-sm);
    padding-top: 0;
  }

  .lotics-form-field__header[data-layout="beside"][data-pitch="text"] {
    --lotics-form-field-rest: calc(var(--lotics-leading-sm) / 2);
  }

  /*
   * ON A PHONE A READ AMONG READS IS ONE LINE: its label at the start, its value at the end, where both fit;
   * a value that does not wraps under its label, at the start (a lone item on a `space-between` line stands
   * there), the label's rung between the two lines. Keyed to the boundary's width: a container query on an
   * undeclared name is false, so `@media` answers the no-boundary case and the two after override it.
   */
  @media (width < 768px) {
    .lotics-form-field[data-layout="stacked"][data-pitch="text"] {
      flex-direction: row;
      flex-wrap: wrap;
      align-items: baseline;
      justify-content: space-between;
      column-gap: var(--lotics-space-16);
      row-gap: var(--lotics-space-6);
    }

    .lotics-form-field__control[data-layout="stacked"][data-pitch="text"] {
      margin-top: 0;
    }

    .lotics-form-field__description[data-pitch="text"],
    .lotics-form-field__warning[data-pitch="text"],
    .lotics-form-field__error[data-pitch="text"] {
      flex-basis: 100%;
    }
  }

  @container lotics-boundary (width >= 768px) {
    .lotics-form-field[data-layout="stacked"][data-pitch="text"] {
      flex-direction: column;
      flex-wrap: nowrap;
      align-items: stretch;
      justify-content: flex-start;
      gap: 0;
    }

    .lotics-form-field__control[data-layout="stacked"][data-pitch="text"] {
      margin-top: var(--lotics-space-6);
    }

    .lotics-form-field__description[data-pitch="text"],
    .lotics-form-field__warning[data-pitch="text"],
    .lotics-form-field__error[data-pitch="text"] {
      flex-basis: auto;
    }
  }

  @container lotics-boundary (width < 768px) {
    .lotics-form-field[data-layout="stacked"][data-pitch="text"] {
      flex-direction: row;
      flex-wrap: wrap;
      align-items: baseline;
      justify-content: space-between;
      column-gap: var(--lotics-space-16);
      row-gap: var(--lotics-space-6);
    }

    .lotics-form-field__control[data-layout="stacked"][data-pitch="text"] {
      margin-top: 0;
    }

    .lotics-form-field__description[data-pitch="text"],
    .lotics-form-field__warning[data-pitch="text"],
    .lotics-form-field__error[data-pitch="text"] {
      flex-basis: 100%;
    }
  }

  /*
   * The boolean row: the control spans both text rows; a toggle has no box, so the trailing rung
   * steps in.
   */
  .lotics-form-field[data-layout="control-first"] {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: var(--lotics-space-8);
    align-items: center;
    padding-bottom: var(--lotics-space-8);
  }

  /* Short of its 8 trailing air by the ring's rung. */
  .lotics-form-field[data-layout="control-first"]::after {
    inset-block-end: var(--lotics-space-4);
  }

  .lotics-form-field[data-layout="control-first"] .lotics-form-field__header {
    grid-column: 2;
    grid-row: 1;
  }

  /* Grid-placed, so the stacked layout's DOM order costs nothing here. */
  .lotics-form-field[data-layout="control-first"] .lotics-form-field__description {
    grid-column: 2;
    grid-row: 2;
    margin-top: 0;
  }

  .lotics-form-field[data-layout="control-first"] .lotics-form-field__control {
    grid-column: 1;
    grid-row: 1 / span 2;
    margin-top: 0;
  }

  /* One line centres on the control; two hang from its top. */
  .lotics-form-field[data-layout="control-first"][data-described] .lotics-form-field__control {
    align-self: start;
  }

  .lotics-form-field[data-layout="control-first"] .lotics-form-field__warning,
  .lotics-form-field[data-layout="control-first"] .lotics-form-field__error {
    grid-column: 1 / -1;
  }
}
