/* Form controls — the three treatments in HANSHI.md (Forms).

   CONTROLS ARE CLASSES, NEVER BARE ELEMENTS. Painting a bare `button`,
   `select` or `label` in this layer beats every rule of the embedded Lexxy
   editor by layer order alone — a later layer wins over any specificity —
   which is exactly how the whole toolbar once rendered as black squares
   (de693f3). Fizzy and Basecamp 5 both keep element rules in `reset` only.
   An element selector is fine when it is scoped inside a component class. */
@layer components {
  /* ——— form treatment a · the written line (dialogs & settings) ———
     the label sits above in tracked caps and a hairline baseline is the whole
     affordance — no box, because a box says "field" a second time.
     everything here is a class: a bare `input` or `select` rule in this layer
     outranks every :where() selector lexxy ships, whatever the specificity. */
  .label {
    display: block;
    margin-block-end: var(--block-space-half);
  }

  /* the label warms while its own control has focus. .field is the wrapper for
     markup that has one; every form today wraps label and control in a plain
     <div>, so the sibling form carries the same rule without a view change. */
  .field:has(:where(.input, .checkbox):focus-visible) .label,
  .label:has(~ :where(.input, .checkbox):focus-visible) {
    color: var(--color-accent-ink);
  }

  .input {
    inline-size: 100%;
    padding-block: var(--block-space-half);
    padding-inline: 0;
    color: var(--color-ink);
    background-color: transparent;
    border: none;
    border-block-end: var(--border);
    border-radius: 0;

    &::placeholder {
      color: var(--color-ink-faint);
    }

    /* the line darkening is the entire hover state */
    &:hover {
      border-block-end-color: var(--color-ink-faint);
    }

    /* focus is the one moment shu appears in a form, so the ring inherited
       from `base` gives way to the baseline that replaces it — --focus-ring is
       reused so the 2px shu mark keeps a single definition. the extra pixel
       comes out of the padding rather than a negative margin: the line lands
       where the hairline was and nothing below the field moves. */
    &:focus-visible {
      outline: none;
      border-block-end: var(--focus-ring);
      padding-block-end: calc(var(--block-space-half) - var(--border-size));
    }
  }

  /* the clear × a search field draws for itself. native means chrome's own
     blue — the one colour in the app nobody chose — and it showed on every
     search field there is: the finding page, the in-room search, the filter
     boxes, every picker's line. repainted rather than removed, because
     clearing a query is worth a control: the same mask, the same ink and the
     same hairline weight every other glyph is painted in (icons.css). */
  .input::-webkit-search-cancel-button {
    appearance: none;
    inline-size: 0.8em;
    block-size: 0.8em;
    background-color: var(--color-ink-faint);
    mask-image: url("/assets/x-8fdf8036.svg");
    mask-position: center;
    mask-repeat: no-repeat;
    mask-size: contain;
    cursor: pointer;

    &:hover {
      background-color: var(--color-ink);
    }
  }

  /* selects keep the written line; the chevron is their only extra mark. it is
     two gradients rather than an svg data uri because a data uri cannot read a
     custom property — a baked stroke colour would not re-ink in dark mode.
     inline-size stays auto: a select that filled its container would push the
     apply button of the filter bar onto a line of its own. */
  .input--select {
    inline-size: auto;
    max-inline-size: 100%;
    appearance: none;
    cursor: pointer;
    padding-inline-end: calc(0.64em + var(--inline-space));
    background-image:
      linear-gradient(45deg, transparent 44%, var(--color-ink-faint) 44% 56%, transparent 56%),
      linear-gradient(135deg, transparent 44%, var(--color-ink-faint) 44% 56%, transparent 56%);
    background-repeat: no-repeat;
    background-position: right 0.32em center, right center;
    background-size: 0.32em 0.32em;
  }

  /* the one control left native: the ua draws the tick and picks its colour
     against accent-color, so it stays legible in both themes where a baked
     svg tick would vanish on dark paper's near-white ink. */
  .checkbox {
    inline-size: 1rem;
    block-size: 1rem;
    accent-color: var(--color-ink);
    cursor: pointer;
  }

  /* the radio, drawn rather than tinted — BC's mark: a hairline circle whose
     border thickens to ink when chosen, the white centre doing the work a
     dot would. accent-color can only recolour the ua's dot, and the dot is
     the wrong mark, so this one leaves native (2026-08-12). The border IS
     the state: nothing appears or disappears, so nothing transitions. */
  .radio {
    appearance: none;
    inline-size: 1.125rem;
    block-size: 1.125rem;
    flex-shrink: 0;
    border: 1.5px solid var(--color-line);
    border-radius: 50%;
    background-color: transparent;
    vertical-align: text-bottom;
    cursor: pointer;

    &:hover {
      border-color: var(--color-ink-faint);
    }

    &:checked {
      border-width: 0.34rem;
      border-color: var(--color-ink);
    }

    /* the ring needs air or it reads as part of the mark */
    &:focus-visible {
      outline-offset: 2px;
    }
  }

  /* ——— boxed, the recorded deviation (HANSHI ## Deviations, 2026-08-12) ———
     "No boxes" holds wherever a field has a label of its own. The to-do
     composer's Due on / Repeat rows are BC's sentence-shaped kind — a date or
     a select sitting mid-sentence, where a written line has nothing to
     underline and a bare value has no affordance at all. Those controls wear
     the box: hairline frame, float radius, content-sized. Focus is the shu
     ring around the frame, because swapping a bottom edge on a box reads as
     the box breaking. */
  .input--boxed {
    /* The box costs no height. A written-line control pays for its rhythm in
       padding alone; a boxed one would add its two edges on top and stand
       taller than the plain rows beside it — which is what a "Due on" block
       of radio rows and date boxes made obvious (user, 2026-08-13). So the
       frame comes out of the padding, and the padding halves again: the type's
       own leading is the breathing room a box does not need to restate.
       Named because :focus-visible has to restore the same value. */
    --boxed-padding-block: calc(var(--block-space-half) / 2 - var(--border-size));
    inline-size: auto;
    padding-block: var(--boxed-padding-block);
    padding-inline: var(--inline-space);
    border: var(--border);
    border-radius: var(--border-radius-float);

    &:hover {
      border-color: var(--color-ink-faint);
    }

    /* The frame stays and the ring goes outside it, so unlike the written
       line there is no edge to swap and no pixel to give back. */
    &:focus-visible {
      outline: var(--focus-ring);
      outline-offset: 1px;
      border-block-end: var(--border);
      padding-block-end: var(--boxed-padding-block);
    }
  }

  /* the boxed select pairs the chevron — BC's ⬍ — the same two-gradient
     trick as .input--select, mirrored once for the upper caret, because a
     data uri still cannot read a custom property. */
  .input--select.input--boxed {
    padding-inline-end: calc(0.64em + var(--inline-space) * 2);
    background-image:
      linear-gradient(135deg, transparent 44%, var(--color-ink-faint) 44% 56%, transparent 56%),
      linear-gradient(45deg, transparent 44%, var(--color-ink-faint) 44% 56%, transparent 56%),
      linear-gradient(45deg, transparent 44%, var(--color-ink-faint) 44% 56%, transparent 56%),
      linear-gradient(135deg, transparent 44%, var(--color-ink-faint) 44% 56%, transparent 56%);
    background-position:
      right calc(var(--inline-space) + 0.26em) top calc(50% - 0.2em),
      right var(--inline-space) top calc(50% - 0.2em),
      right calc(var(--inline-space) + 0.26em) top calc(50% + 0.2em),
      right var(--inline-space) top calc(50% + 0.2em);
    background-size: 0.32em 0.32em;
  }

  /* a short set of radio choices: each one names itself and, underneath, what
     picking it will do. scoped to .choice — never a bare input rule. */
  .choices {
    display: flex;
    flex-direction: column;
    gap: var(--block-space-half);
  }

  .choice {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: baseline;
    gap: var(--inline-space);

    label {
      display: flex;
      flex-direction: column;
      cursor: pointer;
    }

    /* BC's sentence-shaped choice — "At… [16:30]": a choice whose label
       carries a boxed control flows as one line, the words and the box
       side by side. Asked of the label's own child, so a stacked row of
       chips under the words (the day picker) keeps the column. */
    label:has(> .input--boxed) {
      flex-direction: row;
      align-items: center;
      gap: var(--inline-space);
    }
  }

  /* ——— form treatment c · bare voice ———
     the sign-in code is the only thing on its screen, so it is set at display
     size in mono and the written line underneath carries the caret. */
  .code-input {
    font-family: var(--font-mono);
    font-size: var(--text-x-large);
    text-align: center;
    letter-spacing: 0.3em;
    text-transform: uppercase;
  }

  /* Chrome paints an autofilled field in its own baked light blue — fatal on
     dark paper. The 1000px inset shadow is the one paint that wins (chalet's
     defence, verbatim). */
  .input:is(:-webkit-autofill, :-webkit-autofill:hover, :-webkit-autofill:focus) {
    -webkit-text-fill-color: var(--color-ink);
    -webkit-box-shadow: 0 0 0 1000px var(--color-canvas) inset;
    caret-color: var(--color-ink);
  }
}
