/* ——— The to-do composer ———
   BC's form grammar (user, 2026-08-10): a bold label column right-aligned
   against its values, tight rows, no hover washes and no baselines — the grid
   is the structure. One fixed measure shared by every half (the form's
   fields, the subtasks block, the show page's attributes), which is what
   keeps separate <dl>s aligned when HTML won't let them nest in one form.

   Two tenants since 2026-08-14: the card dialog (cards/_form) joins
   `.todo-fields` rather than imitating it — one form grammar, two
   recordables. The `.modal`-scoped retunes at the end of the grid section
   are that tenant's hosting differences, not a fork; if a third form joins,
   that is the moment this family earns a neutral name. */
@layer components {
  .todo-fields {
    /* Exactly "Assigned to" at its 600 weight and nothing more — 94px
       measured against 95.2 — so the whole block hugs the sheet's left edge
       (user, 2026-08-11 — "as far left as the longest word reaches"). In em,
       not rem: the type is fluid and the root is not, so a rem column would
       stand still while the labels grew into it. */
    --todo-label: 5.6em;
    /* BC's density: the rows sit close enough to read as one block. Retuning
       the token is how the fields, the inputs and the placeholders all move
       together (user, 2026-08-10 — "vertically way too much space"). */
    --block-space: 0.75rem;
    /* The dl's field offset — where every row's first line of text sits,
       label and value alike. Resolved HERE, not at use: a var() substitutes
       where the property is declared (_global.css, on the halves), so this
       bakes in the dl's own rhythm and inherits intact past the subtasks
       list's density retune. */
    --field-offset: var(--block-space-half);
    display: grid;
    grid-template-columns: var(--todo-label) 1fr;
    column-gap: var(--inline-space);
    align-items: baseline;
    margin-block: 0;

    > dt {
      font-weight: 600;
      text-align: end;
    }

    /* Anything taller than a line — or whose content can change — leaves the
       baseline: its label holds the top edge, wearing the field offset so its
       text keeps the exact seat baseline alignment gives it at rest (the
       placeholder's and the trigger's own block padding). Fixed geometry,
       because content-derived baselines are how every label hop here
       happened: a chosen pill re-derived the combobox's first baseline from
       its avatar (~1.5px), and an opening reveal swapped baseline for an
       unpadded start pin (~6px). Nothing the dd holds can move the label now
       (user, 2026-08-11).

       Anchored with CHILD combinators, and this is why the misalignment kept
       coming back before that: a descendant like `.todos li` inside :has()
       may resolve its ancestor half OUTSIDE the dd — a row-edit composer sits
       inside ul.todos, so every picker row's <li> matched and start-pinned
       every combobox label. `dd > .todos` pins the list to the dd's own child;
       the reveal only counts once it is open.

       An EMPTY list counts too, and asking for an <li> is how it stopped: a
       checklist with nothing on it yet still has its intake under it, and that
       row is a flex box led by the ghost circle. A flex row that baseline-
       aligns nothing hands its FIRST ITEM's border box up as a baseline, so
       the label lined itself up with the bottom of that circle and sat five
       pixels under the prompt (user, 2026-08-30). Nothing about the <li> was
       load-bearing — the child combinator was. */
    > dt:has(+ dd > :is(.reveal[data-open], .composer, .combobox, .todos)) {
      align-self: start;
      padding-block-start: var(--field-offset);
    }

    /* The subtasks list keeps its own tighter rhythm, which would seat its
       first row's text above the pinned label — so the first row alone wears
       the dl's offset instead of its list's. */
    > dd > .todos > li:first-child .todo {
      padding-block-start: var(--field-offset);
    }

    /* The show page's display values — plain text where the form has
       controls. A control seats its own text (the input's and placeholder's
       block padding); a bare value has nothing, so its rows sat line-on-line
       while the form breathed (user, 2026-08-11). The dd wears the same seat
       instead, and only when none of its children brought one. */
    > dd:not(:has(> :is(.input, .combobox, .composer, .reveal, .todos, .todo-fields__placeholder))) {
      padding-block: var(--field-offset);
    }
  }

  /* In a modal the grid sits outside #main's container, the fluid step
     resolves a shade smaller, and 5.6em stops clearing "When done" by three
     pixels — the label wrapped. A retune, not a second measure: the sheet's
     tuned value above is the todos' own and stays. And a grid TEXT input
     fills its row here — the card dialog's Title is a labelled field like
     the rest (user, 2026-08-14), where the sheet's auto width was sized for
     dates. Element-qualified on purpose: the combobox trigger is a BUTTON
     wearing .input, and stretched full width it shoved its + onto a line of
     its own beside the chosen pills. */
  .modal .todo-fields {
    --todo-label: 6.2em;

    input.input:not(.input--boxed) {
      inline-size: 100%;
    }
  }

  /* ——— Narrow: the label stands over its value ———
     Below the shell's own break the two-column grid stops paying for itself.
     The label column is a fixed 5.6em, so on a 390px sheet it takes a quarter
     of the width and every value wraps under it anyway — the column buys the
     wrapping it was supposed to prevent. Basecamp stacks at this width too
     (user, 2026-08-29, with its phone card page beside ours), and the stack is
     what the sheet already does everywhere else: a label, then the thing.

     A @container query and not a @media one (AGENTS.md): #main gives up the
     panel's width when the panel pushes, so the viewport is asking the wrong
     question — and a modal is a DOM child of #main, so the card dialog and the
     to-do composer answer this query too without a rule of their own.

     35rem is the shell's own number, said once in layout.css and reused here
     rather than a second one invented for the same idea. Below it the sheet is
     100vw, so on a phone the two are the same width exactly.

     Three declarations, because every baseline seat in this file reads
     --field-offset: zeroing the token retires all three at once. That is what
     the token was built for — a variant retunes, it does not restate — and
     restating those :has() selectors here is how the two halves would drift. */
  @container main (width < 35rem) {
    .todo-fields {
      /* Nothing to line a value up with any more: each seat existed to hold a
         value's first line level with the label beside it, and stacked they
         are gaps under the label instead. */
      --field-offset: 0px;

      grid-template-columns: minmax(0, 1fr);

      /* A row is two lines now, so it needs to separate from the next row by
         more than a label separates from its own value: the gap holds the
         pair together and the label's margin doubles it between pairs. The
         block's own top margin is what gives a SECOND <dl> — the subtasks
         list, which is its own frame — the separation its siblings get from
         the gap; without it the checklist read as part of Notes. */
      row-gap: var(--block-space-half);
      margin-block-start: var(--block-space);

      /* The label leads its own line now, so it reads from the margin like
         every other heading on the sheet. Margin and not padding: the seats
         above own this element's padding, at a specificity a plainer selector
         here would lose to. */
      > dt {
        text-align: start;
        margin-block-start: var(--block-space);
      }
    }
  }

  /* No written line inside the grid: the underline is what shoved label and
     value out of alignment (user). Focus brings the shu baseline back
     (inputs.css owns that). Boxed controls opt out — stripping a box's
     bottom edge is the box breaking, not a quieter line. */
  .todo-fields .input:not(.input--boxed) {
    inline-size: auto;
    border-block-end-color: transparent;
  }

  /* Notes and Subtasks before they are asked for: a prompt in faint ink,
     nothing else. Inline-block, because the show page says this as a link
     while the form says it as a button: on an inline <a> the block padding
     paints but sizes nothing, and the sheet's rows collapsed line-on-line
     while the form breathed (user, 2026-08-11). */
  .todo-fields__placeholder {
    display: inline-block;
    padding: 0;
    padding-block: var(--block-space-half);
    border: none;
    background: none;
    font-size: inherit;
    text-align: start;
    color: var(--color-ink-faint);
    cursor: pointer;
  }

  /* The open composer's title row: BC's dotted baseline under the title
     alone. The ghost circle marks the to-do that isn't real yet. */
  .todo--composer .input {
    border-block-end: 1px dotted var(--color-line);
  }

  /* Neither composing row seats its own line: the ROW's padding is the whole
     rhythm, so a composer stands exactly where the row it replaced stood and
     the list never nudges (user, 2026-08-10). The intake was never given the
     rule and so paid the seat twice — its own and the input's — which put the
     first line of an empty checklist a step below every other first line on
     the sheet, where the label pins to --field-offset (user, 2026-08-30). */
  .todo--composer .input,
  .todo--intake .input {
    padding-block: 0;

    &:focus-visible {
      padding-block-end: 0;
    }
  }

  /* The list composer is the same anatomy one voice up; the sheet-headline
     composer wears the h1's own metrics, so editing the title never shrinks
     it (user, 2026-08-10). */
  .todo-composer__title--list {
    font-weight: 600;
  }

  .todo--composer.todo--headline .input {
    font-size: var(--text-x-large);
    font-weight: 600;
  }

  /* Doubled class, not taste: this file sorts before todos.css in the glob,
     so the base .todo-check's `border:` SHORTHAND lands later and reset a
     single-class variant's dashed longhands back to solid — every ghost in
     the app quietly rendered as a real circle (user, 2026-08-11). */
  .todo-check.todo-check--ghost {
    border-style: dashed;
    border-color: var(--color-line);
    pointer-events: none;
  }

  /* ——— Due on / Repeat (BC's block) ———
     Reveals only. Every flex row, gap, wrap and pad is a utility on the
     markup — this is plumbing, and plumbing is what utilities are for
     (AGENTS.md's ladder; user, 2026-08-13). What stays here is which of these
     exist at all, and that is state, which components own.

     None of them may wear a display utility: `utilities` layers after
     `components`, so a `.flex` would beat the :has() rule that hides them.
     They carry the non-display half of their plumbing as utilities instead. */
  .due-modes__on-day,
  .due-modes__on-range {
    display: none;
  }

  .due-modes:has(input[value="day"]:checked) .due-modes__on-day {
    display: revert;
  }

  .due-modes:has(input[value="range"]:checked) .due-modes__range {
    display: inline-flex;
  }

  /* The Repeat row exists only once there is a date to repeat from — and the
     dd has to be revealed AS a column. `display: revert` hands it back to
     `block`, where its gap stops applying without a word: the select and the
     Forever/Until row below it rendered flush against each other while the
     computed row-gap read a healthy 12px, so two attempts to widen the gap
     changed nothing at all (user, 2026-08-13). The dt reverts, the dd does
     not. */
  .todo-fields__repeat {
    display: none;
  }

  .todo-fields:has(.due-modes input[value="day"]:checked),
  .todo-fields:has(.due-modes input[value="range"]:checked) {
    > dt.todo-fields__repeat {
      display: revert;
    }

    > dd.todo-fields__repeat {
      display: flex;
      flex-direction: column;
    }
  }

  .repeat-choice__custom,
  .repeat-choice__ends {
    display: none;
  }

  .repeat-choice:has(.repeat-choice__select option[value="weekly_custom"]:checked) .repeat-choice__custom--weekly,
  .repeat-choice:has(.repeat-choice__select option[value="monthly_custom"]:checked) .repeat-choice__custom--monthly,
  .repeat-choice:has(.repeat-choice__select option:checked:not([value=""])) .repeat-choice__ends {
    display: flex;
  }

  /* ——— the intake ———
     Sheet 09: at rest one row — a dashed circle and a prompt — at the top of
     every list. Open is an explicit state quick-entry sets on focus and holds
     until esc, cancel or a save; :focus-within used to fold a half-written
     to-do the moment anything else was clicked (user, 2026-08-10). */
  .todo--intake .input {
    border-block-end-color: transparent;
  }

  .todo-intake[data-open] .todo--intake .input {
    border-block-end: 1px dotted var(--color-line);
  }

  .todo-intake__more {
    display: none;
    padding-block-end: var(--block-space-half);
    padding-inline-start: calc(1.25rem + var(--inline-space));
  }

  .todo-intake[data-open] .todo-intake__more {
    display: block;
  }

  /* On a narrow sheet the open fold was the whole screen — five labelled
     fields between the line you are typing and the button that saves it
     (user, 2026-08-30). So there the fields wait behind one faint prompt —
     a label over a hidden checkbox, the shell's own CSS-only state — and the
     fold at rest is just the title line and the action row. The toggle
     already wears the placeholder voice in markup; wide screens never show
     it, and a re-arm's pristine restore unchecks it for the next capture.
     A @container query, not @media: #main narrows just the same when the
     notification panel opens (AGENTS.md).

     The pair is toggle + fields as ADJACENT SIBLINGS, not an intake rule:
     the pre-save subtask line inside the card dialog wears the same fold
     (todos/_subtask_line; user, 2026-08-30 — its always-open Assigned to
     and Due on stood directly under the card's own Due on), and scoping to
     .todo-intake would have made it restate all of this. */
  .todo-intake__toggle {
    display: none;
  }

  @container main (width < 35rem) {
    .todo-intake__toggle {
      display: inline-block;
    }

    .todo-intake__toggle:not(:has(.todo-intake__expand:checked)) + .todo-intake__fields {
      display: none;
    }
  }
}
