/* Every native control in the signed-in app: the shared frame a form wears, the
   upload page's file input, and the facts page's grids and roster. Nothing here
   is bare — the rules sit under .app-form, .intake-form, .details-form, or
   .roster, because an element selector like input[type="text"] would otherwise
   outrank a class rule on some other screen. */

/* ==========================================================================
   THE SHARED FRAME — every form in the app carries .app-form and gets these:
   the fieldset and legend, the text input, the select, and the submit row.
   A rule belonging to one form alone stays under that form's own class.
   ========================================================================== */
.app-form { padding-bottom: var(--s8); }
.app-form .fieldset { border: none; margin: 0; padding: 0; margin-top: var(--s7); }
.app-form .fieldset:first-of-type { margin-top: var(--s5); }
/* A section's name is a real <legend>, so `padding` and `width` are reset off
   the browser's own legend rendering before the row is laid out. */
.app-form .legend {
  display: flex; align-items: baseline; gap: var(--s3); flex-wrap: wrap;
  margin-bottom: var(--s3); padding: 0; width: 100%;
}
.app-form .legend h2 { font-size: var(--step-1); font-weight: 700; color: var(--ink); letter-spacing: -0.015em; }
.app-form .legend .lhint { font-size: var(--step--1); color: var(--ink-muted); }
/* A note that runs to a sentence or two stands under the heading, outside the
   legend's flex row, so it reads as a paragraph rather than as a caption
   hanging off a heading. .lhint stays for the short hints that do sit beside
   one. */
.app-form .fnote {
  max-width: 68ch; margin-bottom: var(--s4);
  font-size: var(--step--1); color: var(--ink-muted); line-height: 1.55;
}

.app-form input[type="text"] {
  width: 100%; font-family: var(--sans); font-size: var(--step-0); font-weight: 600;
  color: var(--ink); background: var(--surface);
  border: 1px solid var(--hair-strong); border-radius: var(--radius-sm);
  padding: 12px 14px;
}
/* A placeholder is our own guidance rather than the customer's data, so it is
   set in the page's own face even in a box that renders identifiers in mono:
   "e.g. ASMITH01" then fits a roster column that the mono face overruns. */
.app-form input[type="text"]::placeholder {
  color: var(--ink-ghost); font-family: var(--sans); font-weight: 400; letter-spacing: normal;
}
.app-form input[type="text"]:hover { border-color: var(--ink-faint); }

.app-form .selectwrap { position: relative; max-width: 420px; }
.app-form select {
  -webkit-appearance: none; appearance: none;
  width: 100%; font-family: var(--sans); font-size: var(--step-0); font-weight: 600;
  color: var(--ink); background: var(--surface);
  border: 1px solid var(--hair-strong);
  border-radius: var(--radius-sm); padding: 12px 40px 12px 14px; cursor: pointer;
}
.app-form select:hover { border-color: var(--ink-faint); }
/* CSS-only placeholder: while an empty disabled option is selected a required
   select is :invalid, so it renders ghost; choosing any real option makes the
   select valid and the text renders ink. No JS toggle. */
.app-form select:invalid { color: var(--ink-ghost); font-weight: 400; }
.app-form select:focus-visible { outline-offset: 3px; }
.app-form .selectwrap::after {
  content: ""; position: absolute; right: 15px; top: 50%; width: 8px; height: 8px;
  border-right: 2px solid var(--ink-faint); border-bottom: 2px solid var(--ink-faint);
  transform: translateY(-70%) rotate(45deg); pointer-events: none;
}

/* A box nobody may type in: the fill goes sunk, the ink goes quiet, and the
   pointer says so. Nothing on a screen draws one yet; the gallery does. */
.app-form input:disabled, .app-form select:disabled {
  background: var(--surface-sunk); color: var(--ink-muted); cursor: not-allowed;
}

.app-form .submit-row {
  margin-top: var(--s7); padding-top: var(--s5); border-top: 1px solid var(--hair);
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s4);
}

/* ---- the money prefix ----------------------------------------------------
   The sign a money box wears, drawn inside the box so the box keeps the full
   width of its column. It takes no pointer events, so a press anywhere over it
   lands in the box behind it, and the box carries a left padding wide enough
   to keep the typed number clear of it. */
.app-form .numwrap { position: relative; }
.app-form .numpre {
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  font-size: var(--step-0); font-weight: 600; color: var(--ink-muted);
  pointer-events: none;
}
.app-form input[type="number"].has-pre { padding-left: 30px; }

/* ---- what a refusal looks like -------------------------------------------
   Two surfaces, both in --error: the summary at the top of the form, listing
   every problem as a link to the box it is about, and the sentence printed
   under one box's label. The sentence is what carries the meaning — the color
   is a second signal, never the only one — and the box itself turns its border
   to match so the eye finds it after following a link.

   The summary carries a thick left edge as well as its outline, which is what
   makes it read as the page's one refusal rather than as another framed panel
   among the roster and the flash band. */
.app-form .form-errors {
  margin-bottom: var(--s6); padding: var(--s4) var(--s5);
  border: 2px solid var(--error); border-left-width: 8px;
  border-radius: var(--radius); background: var(--surface);
}
.app-form .form-errors h2 {
  font-size: var(--step-0); font-weight: 700; color: var(--error);
  letter-spacing: -0.015em; margin-bottom: var(--s3);
}
.app-form .form-errors ul { list-style: none; display: grid; gap: 6px; }
/* The links are underlined as well as colored, because a list of links is the
   one place on the page where color alone would have to say "link". */
.app-form .form-errors a {
  color: var(--error); font-weight: 700; font-size: var(--step--1);
  text-decoration: underline; text-underline-offset: 2px;
}

/* The message is drawn only when there is one, and the field group grows to
   fit it. No height is held in reserve: a form that never refuses anything
   carries no empty rows, which is how GOV.UK, Rails, and Basecamp all draw
   this. */
.app-form .field-error {
  font-size: var(--step--1); font-weight: 700; color: var(--error); line-height: 1.4;
}

/* A refused box is drawn with a 2px border in the error color — a real border
   rather than a shadow standing in for one, so it survives a high-contrast
   rendering. The selector names the block the box sits in as well as the box
   itself, because every plain rule for a control here is written as
   `input[type="text"]` or `input[type="number"]`, and an attribute selector
   counts for as much as a class: without the extra step a refused box would
   keep its ordinary border, and would lose the error border again the moment
   the pointer crossed it. The focus outline is untouched: a box the customer is
   typing in states that first. */
.app-form .field-block input.is-error,
.app-form .field-block select.is-error,
.app-form .field-block input.is-error:hover,
.app-form .field-block select.is-error:hover {
  border: 2px solid var(--error);
}

@media (max-width: 480px) {
  .app-form .submit-row .btn { width: 100%; }
}

/* ==========================================================================
   INTAKE FORM (job "new") — what belongs to the upload page alone
   ========================================================================== */
/* The page's three answers are sections of their own rather than fieldsets —
   each is one box under its own heading — so they take the spacing a fieldset
   would have given them, and the first takes the smaller opening gap whether
   or not the summary is drawn above it. */
.intake-form > .field-block { margin-top: var(--s7); }
.intake-form > .field-block:first-of-type { margin-top: var(--s5); }

/* ---- client name field ---- */
.intake-form .textwrap { max-width: 420px; }

/* ---- the client box's suggestions -----------------------------------------
   The names this account has billed before, listed under the box as a plain
   dropdown: no notch, no arrow, nothing to say the box is anything but a box
   the customer types in. It is positioned out of the page's flow, so opening
   it moves neither the file picker nor the portal select under it.

   The list is drawn hidden and the combobox controller shows it, so a browser
   with no JavaScript gets an ordinary text box. */
.intake-form .combobox { position: relative; }
.cblist {
  position: absolute; z-index: var(--z-header);
  top: calc(100% + 4px); left: 0; right: 0;
  max-height: 260px; overflow-y: auto; list-style: none;
  background: var(--surface); border: 1px solid var(--hair-strong);
  border-radius: var(--radius-sm);
  box-shadow: 0 12px 28px -18px oklch(0.30 0.05 264 / 0.6);
}
.cbopt {
  padding: 9px 14px; cursor: pointer;
  font-size: var(--step-0); font-weight: 600; color: var(--ink);
}
.cbopt + .cbopt { border-top: 1px solid var(--hair); }
.cbopt:hover, .cbopt.is-active { background: var(--accent-wash); color: var(--accent-ink); }

/* ---- billing file: native <input type=file> styled to the design system.
   The browser renders the chosen filename after the file-selector button;
   re-picking a file is the input's own built-in behavior. */
.intake-form input[type="file"] {
  width: 100%; max-width: 480px; font-family: var(--sans);
  font-size: var(--step-0); color: var(--ink);
  background: var(--surface); border: 1px solid var(--hair-strong);
  border-radius: var(--radius); padding: var(--s3) var(--s4); cursor: pointer;
}
.intake-form input[type="file"]:hover { border-color: var(--ink-faint); }
/* file-selector button styled as a ghost button */
.intake-form input[type="file"]::file-selector-button {
  font-family: var(--sans); font-size: var(--step--1); font-weight: 600;
  color: var(--ink-body); background: var(--surface-sunk);
  border: 1px solid var(--hair-strong); border-radius: var(--radius-sm);
  padding: 8px 16px; margin-right: var(--s4); cursor: pointer;
}
.intake-form input[type="file"]:hover::file-selector-button { border-color: var(--ink-faint); }

.intake-form .privacy { font-size: var(--step--1); color: var(--ink-muted); max-width: 40ch; line-height: 1.45; }

/* ==========================================================================
   FACTS FORM (a received job's page)
   docs/design/app/job-details-c-roster.html: the short single-value fields sit
   in capped-width grids around the roster, which is the page's anchor — a
   framed table of people, one row each, with a quiet add control on its foot.
   ========================================================================== */
.details-form .fgrid { display: grid; gap: var(--s5) var(--s5); }
.details-form .fgrid-3 { grid-template-columns: repeat(3, minmax(0, 210px)); }
.details-form .fgrid-inv { grid-template-columns: minmax(0, 380px); }
/* The firm's EIN and the client's ID are a pair of identifiers, so they are
   drawn as a pair: two tracks of the same width, and the boxes aligned on
   their bottom edge, so the two controls stay on one line whether or not
   either one is carrying a hint or a refusal above it. */
.details-form .fgrid-ids { grid-template-columns: repeat(2, minmax(0, 240px)); align-items: end; }

/* the shared input rules cover type=text only, and the roster's rate box is the
   one other kind of input this page has */
.details-form input[type="number"] {
  width: 100%; font-family: var(--sans); font-size: var(--step-0); font-weight: 600;
  color: var(--ink); background: var(--surface);
  border: 1px solid var(--hair-strong); border-radius: var(--radius-sm);
  padding: 12px 14px;
}
.details-form input[type="number"]:hover { border-color: var(--ink-faint); }
.details-form input[type="number"] { font-variant-numeric: tabular-nums; }

/* A hint sits between the label and its control, in the same quiet register
   as .lhint; hints exist only where the workshop found a first-timer would
   otherwise guess. */
.details-form .fhint { font-size: var(--step--1); color: var(--ink-muted); }

/* identifiers and codes the portal matches character-for-character */
.details-form input.idin {
  font-family: var(--mono); font-size: 1rem; font-weight: 500; letter-spacing: 0.02em;
}

/* ---- the roster ---- */
.roster {
  margin-top: var(--s4);
  background: var(--surface); border: 1px solid var(--hair-strong);
  border-radius: var(--radius); overflow: hidden;
}
.roster .tkgrid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) 132px minmax(0, 1.2fr) 112px auto;
  gap: var(--s4);
  align-items: end;
}

/* The Remove control's column. The row's boxes are bottom-aligned, and a bare
   text button bottom-aligned against a 40px box reads as sitting under it, so
   this one is lifted to the middle of the box beside it. */
.roster .tkremove { padding-bottom: 11px; }
.roster .tkremove button { white-space: nowrap; }
.roster-head { padding: 10px var(--s5) 8px; background: var(--surface-sunk); border-bottom: 1px solid var(--hair); }
.roster-head .field-lbl { display: block; }

.tkrow { padding: var(--s4) var(--s5); }
.tkrow + .tkrow { border-top: 1px solid var(--hair); }

/* roster controls run one step down from the standalone fields so four of
   them fit across a row without truncating the classification labels */
.roster input, .roster select {
  font-size: var(--step--1) !important; padding: 10px 12px !important;
}
.roster select { padding-right: 34px !important; text-overflow: ellipsis; }
.roster .selectwrap { max-width: none; }
.roster .selectwrap::after { right: 13px; }
.roster input.idin { font-size: 0.92rem !important; }
.roster input.rate { font-variant-numeric: tabular-nums; }
/* The roster's controls run a step smaller than the standalone ones, and the
   rate box's "$" and its padding follow them down. */
.roster .numpre { left: 12px; font-size: var(--step--1); }
.roster input[type="number"].has-pre { padding-left: 26px !important; }

/* Per-cell labels exist for every control. At desktop the column-head row
   shows them, so the in-row copies are visually hidden; under 760px the row
   stacks and the in-row labels become the visible ones. */
.roster .rlab {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

.roster-foot { padding: var(--s3) var(--s5); background: var(--surface-sunk); border-top: 1px solid var(--hair); }

@media (max-width: 760px) {
  .details-form .fgrid-3,
  .details-form .fgrid-ids,
  .details-form .fgrid-inv { grid-template-columns: minmax(0, 1fr); }
  .roster .tkgrid { grid-template-columns: minmax(0, 1fr); gap: var(--s4); }
  /* .roster .tkgrid sets display:grid, so the hide needs equal specificity */
  .roster .roster-head { display: none; }
  .roster .rlab {
    position: static; width: auto; height: auto; clip-path: none;
    display: block; margin-bottom: 5px;
  }
  .tkrow { padding: var(--s5) var(--s4); }
  .roster-head, .roster-foot { padding-inline: var(--s4); }
  /* Stacked, the row's boxes run full width and the Remove control sits under
     the last of them, at its own width rather than stretched across one. */
  .roster .tkremove { padding-bottom: 0; justify-self: start; }
}

/* The wrapper around one control and the small caps label above it. Every
   screen that asks for a value uses this pair, sign-in included. */
.field-block { display: grid; gap: 5px; }
.field-lbl { font-size: 0.72rem; font-weight: 600; letter-spacing: 0.03em; text-transform: uppercase; color: var(--ink-muted); }
