/* ==========================================================================
   FORM
   Labels sit ABOVE the field. A placeholder is never a substitute for a label.
   Error state uses --color-accent-action and is never carried by colour alone
   — an error always renders a text message too (design-rules §18).
   ========================================================================== */

.form { display: flex; flex-direction: column; gap: var(--space-md); }

.field { display: flex; flex-direction: column; gap: var(--space-2xs); }

.field__label {
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-heading);
  color: var(--color-text-body);
}

.field__required {
  color: var(--color-accent-action);
  margin-inline-start: 0.25em;
}

.field__hint {
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
}

.input,
.select,
.textarea {
  inline-size: 100%;
  min-block-size: var(--control-height-input);
  padding: var(--space-xs) var(--space-sm);
  background-color: var(--color-surface-raised);
  border: var(--border-hairline) solid var(--color-border-default);
  border-radius: var(--radius-control);
  color: var(--color-text-heading);
  /* R22b — the box says what it does. A text box gets the I-beam; the two
     <select>s override to `pointer` below. ⚠ THIS ALSO REACHES
     .field__number, which carries .input as a WRAPPER rather than as a real
     control — see the mousedown handler in js/form.js that makes that
     honest. */
  cursor: text;
  font-family: var(--font-family-base);
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  transition: border-color var(--duration-base) var(--ease-standard),
              background-color var(--duration-base) var(--ease-standard);
}

.input::placeholder,
.textarea::placeholder { color: var(--color-text-muted); }

/* --------------------------------------------------------------------------
   SELECT CHEVRON — R21x, from a supplied asset.

   ⚠⚠ THIS IS THE PAGE'S ONLY `data:` URI ICON AND §9 PROHIBITS THEM. The
   amendment and the full reasoning are in design-rules.md §9; the short
   version is that the rule's own remedy — inline the SVG, or mask it, so
   `currentColor` resolves — DOES NOT EXIST FOR A <select>. It is a replaced
   element and hosts no ::before or ::after in any engine, and masking the
   control itself would clip the whole field to the chevron. Once
   `appearance: none` takes the native arrow away, `background-image` is the
   only layer left, and it cannot read `currentColor` by any route.

   SO THE COLOUR IS BAKED, WHICH IS WHY THE RULE'S FAILURE MODE CANNOT HAPPEN:
   the stroke is #B4B4B4 at 5.74:1 on the field ground, not an unresolved
   `currentColor` falling back to black. Both selects sit on that same ground,
   so there is no second surface for it to be wrong on.

   ⚠ #B4B4B4 IS --color-text-body WRITTEN OUT AS A LITERAL. A data: URI cannot
   read a custom property. Move that token and this hex will NOT follow it and
   nothing will report the drift — it is the one colour on the page unreachable
   from tokens.css. Grep for it if the greys ever change.

   URL-ENCODED, NOT BASE64, on purpose: the asset arrived as base64, which
   hides the markup entirely. This form is readable and greppable. The inert
   `M0 0h24v24H0z` path the asset carried is stripped, exactly as §9 requires
   of an inlined icon — it drew nothing.

   THE PADDING IS THE BREATHING SPACE. The chevron is inset --space-sm from
   the edge, the SAME 16px as the field's own text padding on the left, so it
   sits on the field's margin rather than against its border; and the inline-end
   padding is (2 x --space-sm + the icon), so a long option label stops one
   full 16px short of the chevron instead of running under it.
   -------------------------------------------------------------------------- */
.select {
  /* R22b — a select is chosen from, not typed into. `appearance: none` below
     takes the native control away, and with it the arrow cursor the OS would
     have supplied; this puts the affordance back. */
  cursor: pointer;
  -webkit-appearance: none;
          appearance: none;
  padding-inline-end: calc(var(--space-sm) * 2 + var(--icon-size-inline));
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23B4B4B4' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m6 10l6 6l6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-sm) center;
  background-size: var(--icon-size-inline) var(--icon-size-inline);
}

.input:hover,
.select:hover,
.textarea:hover { border-color: var(--color-border-interactive); }

/* ⚠⚠ R21t — THE FOCUS RING IS REMOVED FROM FORM FIELDS, BY DIRECTION, AND
   THIS IS A KNOWN WCAG 2.4.7 FAILURE. base.css said "never removed, never
   clipped"; design-rules.md §9 now carries the amendment and the measurement.

   WHAT IS LEFT IS NOT A SUBSTITUTE AND SHOULD NOT BE MISTAKEN FOR ONE. The
   two declarations below step the border 0.10 -> 0.20 white and the ground
   #0A0A0A -> #121212: about 1.3:1 over the field's own ground, against the
   3:1 WCAG 2.4.11 asks of a focus indicator. A keyboard-only reader cannot
   tell which of the seven fields they are in.

   ⚠ IT IS INVISIBLE IN REVIEW. A mouse user never notices, because they click
   the field they are already looking at. Only tabbing exposes it.

   ⚠ SCOPED TO FORM CONTROLS. Buttons, links and the modal close keep the ring
   — base.css's :focus-visible still reaches them. Do NOT extend this for
   consistency; that turns one section's failure into the page's.

   THE WAY BACK, costed and declined once: swap `outline: none` for
   `border-color: var(--color-accent-identity)` at 2px — 4.95:1 against the
   field ground, no floating box, 2.4.7 satisfied. */
.input:focus-visible,
.select:focus-visible,
.textarea:focus-visible,
.field__digits:focus-visible {
  outline: none;
  border-color: var(--color-border-strong);
  background-color: var(--color-surface-card);
}

/* --------------------------------------------------------------------------
   ERROR / SUCCESS
   -------------------------------------------------------------------------- */
.field[data-state="error"] .input,
.field[data-state="error"] .select,
.field[data-state="error"] .textarea { border-color: var(--color-accent-action); }

.field__message {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  font-size: var(--font-size-meta);
  color: var(--color-accent-action);
}

.field[data-state="success"] .field__message { color: var(--color-accent-confirm); }

/* R21e — THE ONLY RULE ADDED TO WIRE THE FORM. .field__message is
   `display: flex`, which beats the UA's `[hidden] { display: none }` on
   specificity, so a message node sitting in the markup with `hidden` set
   STILL PAINTS — as an empty flex box that spends .field's 8px gap under
   every field on the page. The attribute has to be honoured explicitly.

   ⚠ NOT :empty. Toggling on emptiness would hide a message that is about to
   be filled and reveal one mid-write; `hidden` is the state js/form.js
   actually sets, so it is the state that should be styled. */
.field__message[hidden] { display: none; }

/* --------------------------------------------------------------------------
   CHECKBOX — consent
   Tap target meets the 48px floor via the label's padding block.
   -------------------------------------------------------------------------- */
/* ⚠⚠ NO CONSUMER as of R9u — the consent row was removed from the modal by
   direction. These rules are kept because the row HAS TO COME BACK: the form
   collects a name, work email, institution and job title, and §14 lists a
   consent line with a linked policy as part of the modal. The styling is
   ready; only the copy and the policy URL are missing. */
/* ⚠⚠ `gap: 0` IS LOAD-BEARING AND MUST NOT BE DELETED AS REDUNDANT.

   The 12px between the box and its label used to be flex gap, which belongs
   to NEITHER control: the cursor fell back to an arrow crossing it and a click
   there did nothing. It is padding on the label now, so the same 12px is part
   of the label's own box — pointer the whole way across, and clicking toggles.

   ⚠ R22a REMOVED THE `gap` DECLARATION AND THAT WAS NOT ENOUGH. The rule here
   read `gap: var(--space-xs)` (12px), which existed to OVERRIDE .field's base
   `gap: var(--space-2xs)` (8px). Deleting it did not remove the gap — it
   revealed the smaller one underneath, leaving an 11px dead strip that
   measured as `cursor: auto` between the box at x953 and the label at x964.
   Zeroing it is what actually closes the strip.

   THE SPACING IS UNCHANGED EITHER WAY: 12 gap + 0 padding, or 0 gap + 12
   padding. Same pixels, one of them clickable. */
.field--consent {
  flex-direction: row;
  align-items: flex-start;
  gap: 0;
}

.checkbox {
  flex: 0 0 auto;
  inline-size: var(--checkbox-size);
  block-size: var(--checkbox-size);
  margin-block-start: calc((var(--font-size-meta) * var(--line-height-body) - var(--checkbox-size)) / 2);
  -webkit-appearance: none;
  appearance: none;
  background-color: var(--color-surface-raised);
  border: var(--border-hairline) solid var(--color-border-interactive);
  border-radius: var(--radius-control);
  cursor: pointer;
  display: grid;
  place-content: center;
  transition: background-color var(--duration-base) var(--ease-standard),
              border-color     var(--duration-base) var(--ease-standard);
}

/* R21y — THE MARK IS THE PAGE'S OWN TICK NOW. R21l drew one out of two
   rotated borders because §9 forbids a `data:` URI icon and an <input> is a
   void element that cannot hold an <svg>. A supplied asset replaces it.

   ✅ AND IT TURNED OUT NOT TO BE A NEW ASSET AT ALL. The supplied polyline —
   points="2.75 8.75 6.25 12.25 13.25 4.75" — is byte-for-byte the one §4's
   four .contrast__badge--win markers already carry as .marker__glyph--tick,
   on the same `viewBox="0 0.5 16 16"`. This is the page's existing tick, in a
   fifth place, not a second tick that happens to look similar.

   ⚠⚠ AND THIS ONE DOES NOT BREAK §9, UNLIKE THE SELECT'S CHEVRON. A mask reads
   only ALPHA — the colour in the URI is discarded — and the visible colour
   comes from `background-color`, which is a real token. So `currentColor` is
   not being asked to resolve inside a data: URI, and the rule's failure mode
   (an icon that renders black on every surface) cannot occur. The stroke is
   written as #000 rather than left as currentColor purely so the alpha is
   unambiguously 1 where the ink is; nothing reads that value.

   ⚠ A CHECKBOX CAN HOST ::before AND THAT IS THE WHOLE DIFFERENCE. The select
   cannot, which is why its chevron had to bake a hex and take an amendment,
   and this does not. Do not cite that amendment to justify a baked colour
   anywhere a pseudo-element exists.

   THE SIZE IS DERIVED: 1.75 x --checkbox-mark-size is 14px inside the 20px
   box's 18px interior, so the ink lands about 9 x 7px with 2px of air each
   side. It re-derives if the token moves.

   CONTRAST: --color-on-action #0F0F0F on #3BFF9A is 14.6:1, unchanged from
   R21l — far past the 3:1 a non-text indicator needs. */
.checkbox::before {
  content: "";
  inline-size: calc(var(--checkbox-mark-size) * 1.75);
  block-size: calc(var(--checkbox-mark-size) * 1.75);
  background-color: var(--color-on-action);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0.5 16 16'%3E%3Cpolyline fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' points='2.75 8.75 6.25 12.25 13.25 4.75'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0.5 16 16'%3E%3Cpolyline fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' points='2.75 8.75 6.25 12.25 13.25 4.75'/%3E%3C/svg%3E");
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
  transform: scale(0);
  transition: transform var(--duration-fast) var(--ease-standard);
}

/* ⚠⚠ RESTORED AT R21z AFTER R21y DELETED IT BY ACCIDENT. The R21y edit
   replaced a RANGE of lines — from the old tick's comment through
   `.checkbox:checked::before` — and this rule sat inside that range, between
   the two it meant to replace. Nothing caught it: braces balanced, comments
   balanced, no duplicate selectors, and the file parsed.

   THE SYMPTOM WAS NOT "THE FILL IS MISSING". The box still toggled and the
   tick still scaled in — but the tick is --color-on-action #0F0F0F and the
   unchecked ground is --color-surface-raised #0A0A0A, which is 1.03:1. It
   read as a checkbox that could not be ticked at all. That is the same
   invisible pairing CLAUDE.md has now been bitten by six times.

   THE LESSON IS THE SPLICE, NOT THE RULE: replacing a line range assumes
   nothing unrelated lives inside it. Anchor on both ends and check what is
   between them. */
.checkbox:checked {
  background-color: var(--color-accent-confirm);
  border-color: var(--color-accent-confirm);
}

.checkbox:checked::before { transform: scale(1); }

.field--consent .field__label {
  padding-inline-start: var(--space-xs);
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
  /* On the box too — see .checkbox. Between them they now cover every pixel
     of the control, which is what makes the row read as one thing. */
  cursor: pointer;
  min-block-size: var(--space-md);
}

.field--consent .field__label a { color: var(--color-text-body); text-decoration: underline; }

/* --------------------------------------------------------------------------
   ROW — R21h, the two new classes in this file and the only ones the rebuilt
   registration form needed.

   .form__row pairs two fields on one line. TWO ROWS, NOT THREE, since R21n
   unpaired email and phone by direction: first/last name, and
   institution/job title. Email, phone and department each take a whole line.

   ⚠ THE BREAKPOINT IS 768 BECAUSE THAT IS THE MODAL'S OWN. Below it the panel
   is a full-screen sheet and the columns would be ~159px on a 390px phone —
   narrower than the labels above them. At and above it the panel is a fixed
   side panel: 560px max less 2 x 48px inline padding leaves 464px, so two
   220px columns with a 24px gutter. Neither number is picked; both fall out
   of --modal-panel-max and --container-padding-inline.

   The gap is --space-md, matching .form's own gap exactly, so a stacked row
   at mobile is indistinguishable from the fields above and below it.
   -------------------------------------------------------------------------- */
.form__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md);
}

@media (min-width: 768px) {
  .form__row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ⚠⚠ R21x — THE SUBGRID THAT USED TO SIT HERE IS GONE, AND IT WAS WHY THE
   PAIRED FIELDS WERE TALLER THAN THE ONES ON THEIR OWN ROWS.

   R21v added it so a two-line label could not push its input out of line with
   its partner's. The cost was invisible until it was rendered: a subgrid spans
   the PARENT's tracks, the parent's row-gap is --space-md, and the field's own
   8px re-declaration does not shrink the parent's tracks — the leftover is
   absorbed by the items, which stretch. Every field in a row came out about
   16px (24 - 8) taller than Email.

   IT IS NOT NEEDED ANY MORE. R21w shortened "Name of the Institution you are
   part of" to "Name of the Institution", and with that NO LABEL WRAPS at the
   column's ~220px. All six fields are back to the component's own 52px.

   ⚠ IF A LABEL EVER WRAPS AGAIN the pair will misalign, and the fix is NOT to
   restore this block as it was. Either shorten the label, or restore the
   subgrid AND set the parent's row-gap to --space-2xs so the tracks and the
   field agree. */

/* TWO CONTROLS, ONE FIELD — R21i, the phone prefix beside the number.

   ⚠ THE SELECT IS THE NARROWER HALF ON PURPOSE. The number is what the reader
   types; the country is chosen once and then only confirmed. min-inline-size:0
   on both is load-bearing: a flex item's default min-width is `auto`, and a
   <select> holding a 248-item list reports a very wide intrinsic minimum, so
   without this it refuses to shrink and pushes the number input out of the
   column entirely.

   ✅ R21n GAVE PHONE ITS OWN FULL-WIDTH ROW, which is the fix this note used
   to recommend for the truncation it used to describe. The select went from
   ~99px of a shared half-column to ~209px of a 464px field, so most country
   names now read whole when closed. Two things still cover the long ones: the
   dial code lives in the number box's placeholder (R21j), so the functional
   half of the answer never depends on the name, and the full name is always
   whole in the open list.

   TYPE-AHEAD IS WHY THE LABELS LEAD WITH THE ENGLISH NAME rather than the
   dial code — typing "un" jumps to the United entries, and on a 234-item list
   that is the only practical way to navigate it. */
.field__group {
  display: flex;
  gap: var(--space-2xs);
}

.field__group .select { flex: 0 1 36%; min-inline-size: 0; }
.field__group .input  { flex: 1 1 64%; min-inline-size: 0; }

/* --------------------------------------------------------------------------
   THE NUMBER BOX — R21p. Two things inside one bordered field: a standing
   dial code and the digits after it.

   ⚠ .field__number CARRIES .input IN THE MARKUP, so the border, ground,
   height, padding, hover and error states are the component's and are not
   re-described here. This block only turns that box into a flex row and
   strips the real <input> back to nothing.
   -------------------------------------------------------------------------- */
.field__number {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
}

/* Grey and untouched. --color-text-muted on --color-surface-raised is 5.74:1,
   past the 4.5:1 this needs as real text — it is information, not decoration.
   `user-select: none` so dragging across the field to retype the number does
   not carry the code into the clipboard; flex: none so a long code like +1264
   is never squeezed by the digits beside it. */
.field__code {
  flex: none;
  color: var(--color-text-muted);
  user-select: none;
}

/* reset.css already gives form controls `font: inherit`; everything else the
   UA draws has to go, because the box around it is the wrapper's now. */
.field__digits {
  flex: 1 1 auto;
  min-inline-size: 0;
  padding: 0;
  background-color: transparent;
  border: 0;
  color: inherit;
}

/* Mirrors the focus rule above, which cannot reach a wrapper the reader never
   focuses directly. R21t deleted the @supports :has() block that used to sit
   here: its only job was to move the ring from the digits onto this wrapper,
   and there is no longer a ring to move. */
.field__number:focus-within {
  border-color: var(--color-border-strong);
  background-color: var(--color-surface-card);
}

/* CONSENT BLOCK — four consecutive children at .form's 24px gap read as four
   unrelated statements. They are one block, so they get their own tighter
   interval and the 24px falls between the block and what surrounds it. */
.form__consent {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

/* --------------------------------------------------------------------------
   SUBMIT
   Uses .btn.btn--primary.btn--block. This block only adds spacing above it.
   -------------------------------------------------------------------------- */
.form__actions { margin-block-start: var(--space-2xs); }
