/*
 * Form layer — styles for the {% field %} renderer, the dense form grid,
 * Django's default error output, and form-widget utility classes that
 * Python code already assigns (w-full, monospace).
 * Tokens come from tokens.css; no :root declarations here.
 */

/* ---- Field unit (rendered by ui/field.html) ---- */
.field{display:flex;flex-direction:column;gap:4px;min-width:0}
.field label{display:flex;flex-direction:column;gap:6px;margin:0;font-size:var(--fs-1);color:var(--muted)}
.field .field-label{display:inline-flex;align-items:center;gap:6px}
.field .req{color:var(--danger);font-weight:700}
.field .field-lock{font-size:11px;opacity:.7}
.field .field-value{display:flex;align-items:center;padding:0 var(--control-pad-x);border-radius:var(--r-2);border:1px solid var(--field-border);background:var(--field-readonly-bg);color:var(--field-ink);min-height:var(--control-h);font-size:var(--fs-1)}

/* Editability is encoded as FILL: readonly fields sit on panel grey. */
.field--readonly input,
.field--readonly textarea,
.field--readonly select{background:var(--field-readonly-bg);color:var(--muted)}

.field--error input,
.field--error textarea,
.field--error select{border-color:var(--danger)}
/* `job_editor.js:608` marks a section title that is required-but-empty with
   this class, and nothing defined it -- so the toast said "Please add a title
   for: X" while X itself carried no indication at all. Same treatment as
   `.field--error` above, which is the house idiom for an invalid control. */
.required-missing{border-color:var(--danger)}
.field-error{margin:0;font-size:var(--fs-1);color:var(--danger-ink)}

/* The control is a sibling of the label now, so the ROW layout belongs to the
   field, not to the label.
   `display:flex` is RESTATED here on purpose. Inside a band host the cell is
   turned into a subgrid (`display:grid`), on which `flex-direction` and
   `justify-content` are inert -- so the checkbox landed in the CONTROL band
   with its caption in the LABEL band, which is the stranding this rule exists
   to undo. It was live on `people_form.html:138`, a `{% field %}` checkbox
   directly inside `.form-grid.cols-4`.
   `--band:1` because a checkbox row occupies ONE band: it has no separate
   caption to sit above it. */
.field.field--check{display:flex;--band:1;flex-direction:row-reverse;align-items:center;justify-content:flex-end;gap:8px}
.field--check label{flex-direction:row;align-items:center;gap:8px}
.field--check input{width:auto}
/* Generalises the rule above to hand-written checkboxes. `app.css` makes every
   <label> a COLUMN flexbox, which strands a checkbox above its own caption --
   11 filter panels do this. The intended undo (`.filters .checkbox`) reaches
   exactly ONE element in the tree, and `.checkbox-label` (5 uses) is defined
   nowhere, so in practice the defect was unfixed everywhere it appeared. */
label:where(:has(> input[type=checkbox], > input[type=radio])){flex-direction:row;align-items:center;gap:var(--space-2);width:max-content;max-width:100%}

/* Composer skill picker options: labels default to column direction (app.css),
   which strands the checkbox above the skill name. Interim until the P2
   bubble UI replaces the picker. */
.composer-skill-option{flex-direction:row;align-items:center;gap:8px;margin:2px 0}
.composer-skill-option input{width:auto}

/* `:where(:not([hidden]))` and not a bare `:not([hidden])` (S-F.5).
   An author `display` rule beats the UA `[hidden]{display:none}` by cascade
   ORIGIN, so this rule was un-hiding every element the page JS hides by
   attribute. `:where()` contributes ZERO specificity, so the scope is
   provably free of cascade side effects — a bare `:not([hidden])` would
   raise this rule and change which of its neighbours wins. */
.help:where(:not([hidden])){display:block;margin:0;font-size:var(--fs-1);color:var(--muted)}

/* ---- Dense form grid ---- */
.form-grid{display:grid;gap:var(--space-2) var(--space-3);grid-template-columns:repeat(var(--cols,4),minmax(0,1fr));align-items:start}
/* ---- The label band — ONE definition, any container may adopt it --------
   Two shelves. Band 1 is sized by the tallest LABEL in the row, band 2 by the
   tallest CONTROL. A cell with no label leaves band 1 empty, so a bare button
   or popout starts level with its neighbours' CONTROL rather than with their
   caption -- and STAYS level when a caption wraps to two lines. That is why a
   fixed band height is not the fix, and why `align-items:end` is not either:
   `end` aligns to whatever comes LAST in a cell, which is the help text or the
   error message, not the control.

   Extracted from `.form-grid` so it is a primitive rather than a property of
   one container. To band a new row, add its class to the BAND_HOSTS list
   below and nothing else -- that is the whole opt-in. `.control-row` is the
   generic host for rows that are not a dense form grid.

   Kept as a real class list rather than `:where(...)`: `:where()` contributes
   zero specificity, which would drop these below rules that currently lose to
   them, and this block decides layout.

   `--band` is the span, so a cell opts out with `--band:1` rather than needing
   a second selector -- the same shape as `--cols` and `--row-cols`.

   `subgrid` computes to `none` when the parent axis is not a grid, so a
   `.field` inside `.create-form`, a `<td>` or a `.field-bubble` degrades to a
   plain two-row grid, visually identical to the flex column it replaces. */
.form-grid,
.control-row,
.email-with-action{align-items:start}
.form-grid > *,
.control-row > *,
.email-with-action > *{grid-row:span var(--band,2)}
.form-grid > :where(.field,.form-cell),
.control-row > :where(.field,.form-cell),
.email-with-action > :where(.field,.form-cell){display:grid;grid-template-rows:subgrid;gap:var(--space-1)}
/* Third band, two declarations. `has-notes` is `ui/field.html` saying so
   itself — the class and the `.field-notes` child are guarded by the same
   condition, so the band always matches the child count, whatever children
   a field grows next and in browsers without `:has()`. The `:has(> .help)`
   form stays for hand-built cells that put a bare `.help` in a band host.
   Two RULES, not one selector list: a browser without `:has()` drops an
   entire list that contains one, which would take `has-notes` down with it. */
.form-grid > :where(.field,.form-cell).has-notes,
.control-row > :where(.field,.form-cell).has-notes,
.email-with-action > :where(.field,.form-cell).has-notes{--band:3}
.form-grid > :where(.field,.form-cell):has(> .help),
.control-row > :where(.field,.form-cell):has(> .help),
.email-with-action > :where(.field,.form-cell):has(> .help){--band:3}
.form-grid > :where(:not(.field):not(.form-cell)),
.control-row > :where(:not(.field):not(.form-cell)),
.email-with-action > :where(:not(.field):not(.form-cell)){align-self:end}
.form-grid > :where(.band-off),
.control-row > :where(.band-off),
.email-with-action > :where(.band-off){--band:1}
/* A `<details>` cell is prose, not a labelled control: as a subgrid its
   summary and body would share tracks (the body lands ON the summary when
   opened). It flows as a block and, with `band-off`, spans one row that
   grows with it. */
.form-grid > :where(details.field),
.control-row > :where(details.field){display:block}
/* Errors and help stack inside the notes track, never on the control. */
.field-notes{display:grid;gap:var(--space-1);align-content:start}

/* The generic banded row: a line of controls that is not a dense form grid.
   Columns size to content; at the narrow breakpoint it stacks, matching
   `.form-grid`'s own behaviour rather than inventing a second one. */
.control-row{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,max-content);gap:var(--space-1) var(--space-3)}
@media(max-width:560px){.control-row{grid-auto-flow:row}}
/* :where() keeps the modifiers at .form-grid's own specificity so the
   max-width media rules below (same specificity, later in the file) can
   actually collapse them — plain .form-grid.cols-N silently beat the
   breakpoints and pinned every modified grid at its desktop column count. */
.form-grid:where(.cols-2){--cols:2}
.form-grid:where(.cols-3){--cols:3}
.form-grid:where(.cols-6){--cols:6}
.field.span-2{grid-column:span 2}
.field.span-3{grid-column:span 3}
.field.span-full{grid-column:1/-1}
@media(max-width:880px){
  .form-grid{--cols:2}
  .field.span-3{grid-column:span 2}
}
@media(max-width:560px){
  .form-grid{--cols:1}
  .form-grid .field{grid-column:1/-1}
}

/* Email field + inline Change-e-mail button.

   RULING REVERSED (owner, 2026-08-17). This comment used to end "Do not add a
   min-height to fake a match", and it was right that faking one is wrong --
   but it drew the wrong conclusion from it. It bottom-aligned the button to
   the FIELD, and a field is taller than its input by however much help text
   hangs underneath, so the button sat 45.79px below the input it belongs to.

   Nothing is faked here. The row is a band host (above), so the button lands
   in the CONTROL band next to the input, and the help text still hangs below
   both. That is the distinction the old comment was reaching for: align to the
   control, not to the container, and never by inventing a height. */
.email-with-action{grid-column:span 2;display:grid;grid-template-columns:1fr auto;gap:var(--space-2)}
.email-with-action > .field{min-width:0}
.email-action{display:flex;align-items:stretch}
@media(max-width:560px){.email-with-action{grid-column:1/-1}}

/* ---- Narrow create-form pages ---- */
.create-form{display:grid;gap:var(--space-3);max-width:800px}
/* Short auth flows (login, password resets): one narrow column. */
.auth-narrow{max-width:480px}
.auth-narrow .form-grid{--cols:1}
.create-form h2{margin:var(--space-2) 0 0}
.create-actions{display:flex;gap:var(--space-2)}
.create-actions .gdpr-send-disabled-reason{align-self:center;margin:0;font-size:var(--fs-1)}
.gdpr-status-line{margin:var(--space-2) 0 0;font-size:var(--fs-2)}

/* ---- Sticky form actions bar ---- */
.form-actions{position:sticky;bottom:0;z-index:20;display:flex;gap:var(--space-2);align-items:center;justify-content:flex-end;padding:var(--space-2) var(--space-3);background:rgba(var(--surface-rgb)/.92);backdrop-filter:blur(4px);border-top:1px solid var(--border)}
.form-actions .dirty-note{margin-right:auto;font-size:var(--fs-1);color:var(--muted)}

/* ---- Error output ---- */
/* Styles Django's default errorlist so unconverted as_p pages improve for free. */
ul.errorlist{list-style:none;margin:0 0 4px;padding:0}
ul.errorlist li{margin:2px 0;padding:6px 8px;border:1px solid var(--danger);border-radius:var(--r-1);background:var(--danger-soft);color:var(--danger-ink);font-size:var(--fs-1)}
.form-errors{margin:0 0 var(--space-3);padding:var(--space-2) var(--space-3);border:1px solid var(--danger);border-radius:var(--r-2);background:var(--danger-soft);color:var(--danger-ink)}
.form-errors ul{margin:0;padding-left:18px}

/* ---- Widget utility classes already set by Python form code ---- */
.w-full{width:100%}
.monospace{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
