/* =============================================================================
   fields.css — the single definition for shared interface CONTROLS: every
   text-entry field (2026-08-05), and the secondary button (2026-08-09).

   Both landed here for the same reason. This is the only stylesheet loaded
   LAST on every page, which is what lets a rule here settle a value the
   ported reference sheet also sets, by plain source order rather than
   !important or a specificity hack. See "LOAD ORDER IS LOAD-BEARING" below.
   The secondary-button section is at the bottom of this file.

   WHY THIS FILE EXISTS

   Field styling had accumulated across 8 stylesheets: 31 rules, 7 different
   border colours and 8 different fills. Measured in-browser, 5 of the app's
   7 page families rendered fields below the 3:1 that WCAG 1.4.11 asks for the
   boundary of an interface control — including the sign-in page at 1.29:1,
   the first screen every user sees. Three customers reported the same thing
   in different words ("couldn't tell what the text inputs were"), and each
   was fixed by patching the one page they were looking at, which produced
   three byte-identical copies of the same two colour values and left every
   other page family broken.

   The measurements that made this a real defect rather than a preference:
     - A browser's OWN default field border is 4.54:1 and passes comfortably.
       Every custom value in this app scored 1.29–1.52. Where our CSS applied,
       it made fields harder to see than shipping nothing would have.
     - Fields inheriting a 0.9rem wrapper font computed to 14.4px, and iOS
       Safari zooms the whole page whenever a focused control is under 16px.

   LOAD ORDER IS LOAD-BEARING

   This file must be the LAST stylesheet on every page. It restates the same
   wrapper selectors that already exist rather than out-shouting them with
   !important (rejected everywhere in this codebase) or with specificity
   hacks. tests/dom/fieldStyling.test.ts asserts both halves of that: that
   every field-bearing page links it, and that it is linked last.

   THE :not() EXCLUSIONS ARE NOT SPECIFICITY PADDING

   Each one is a control that genuinely must not get a text-box: checkboxes,
   radios, file pickers and buttons are a different control class. They do
   also raise specificity, which is what lets the wrapper layer below tie
   with existing type-qualified rules like `.repeatable-row input[type="text"]`
   (0,2,1) — a plain `.repeatable-row input` at (0,1,1) would lose to it no
   matter how late this file loaded. That distinction cost a real debugging
   pass on the weekly report earlier today: a bare .weekly-editor__field at
   (0,1,0) silently lost to `.daily-report-form textarea` at (0,1,1), and the
   page looked plausible until the computed border was actually read.
   ========================================================================== */

:root {
  /* Deliberately NOT named --field-bg: style.css already defines that as
     #f8fafc, one of the failing values this file replaces. */
  --control-fill: #eef3f2;
  --control-border: #6b8480;
  --control-text: #12313d;
  --control-placeholder: #5a6b6e;
  --control-focus-border: #155e54;
  --control-focus-ring: rgba(21, 94, 84, 0.15);
  --control-radius: 10px;

  /* ------------------------------------------------------------------------
     --text-secondary override (2026-08-09) — WCAG 1.4.3

     daily-report-reference.css:14 defines this as #687b82. Measured against
     every light background secondary text actually sits on in this app:

       control fill  #eef3f2   3.95   <- worst case
       highlight     #f0f7f4   4.07
       page ground   #f3f7f6   4.10
       surface-muted #f7faf9   4.22
       resource grad #f8fbfa   4.25
       white         #ffffff   4.43

     All six are under the 4.5:1 AA floor for normal-size text, and every one
     of the 138 usages is `color` (no borders, no backgrounds — checked), on
     text measured at 12.16px-16px at weight 400, so the large-text 3:1
     exception never applies. This is the colour of every
     .page-heading__description, .form-help and muted label in the product.

     WHY THE TOKEN AND NOT THE 138 CALL SITES

     The token is defined once, in the reference stylesheet — which is a
     verbatim port that must never be hand-edited (two prior surgical edits
     silently truncated it, killing ~800 rules with no error). Redefining it
     here instead is a one-line change that reaches all 138 usages, and works
     because :root here and :root there are both (0,1,0) while this file loads
     last. Editing the call sites would mean touching six stylesheets.

     WHY DARKENING IS SAFE

     Verified empirically before changing it, not assumed: scanned real
     rendered pages for every element computing to #687b82 and resolved each
     one's effective background by walking its ancestors. Only two backgrounds
     appeared (white and the page ground) and `any_dark_background` was false.
     The app's dark surfaces — .detail-card__number, .primary-action, the
     stat-tile accent bars — all set their own explicit light text, so nothing
     gets worse by making this darker.

     WHY THIS EXACT VALUE

     #53676e is not a new colour: it is the reference sheet's own
     --neutral-text. Worst case 5.30:1, so it clears AA on all six backgrounds
     with real margin rather than landing on the threshold (#5f7278 measured
     exactly 4.50 and was rejected for that reason). Reusing an existing
     palette token also unifies it with .empty-note in style.css, which was
     given --neutral-text in an earlier pass precisely because
     --text-secondary was failing — those two no longer disagree.

     Hierarchy is preserved: --text-primary (#12313d) is 13.7:1 on white
     against this 5.94:1, still a clear 2.3x separation.
     ------------------------------------------------------------------------ */
  --text-secondary: #53676e;
}

/* Wrappers that carry a field anywhere in the app. Derived from an audit of
   every existing rule that set a border or background on a control, not from
   memory — a wrapper missing here silently keeps its old failing style. */

/* --------------------------------------------------------------------------
   Resting state
   -------------------------------------------------------------------------- */
input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]):not([type="range"]):not([type="color"]),
select:not([hidden]),
textarea:not([hidden]),
:is(
  .form-group,
  .form-field,
  .data-form,
  .daily-report-form,
  .inline-form,
  .repeatable-row,
  .repeatable-row__field,
  .review-actions,
  .submitted-report__return-control,
  .purge-confirm__body,
  .user-manage-suspend-form,
  .assignment-revoke-form,
  .director-add-form
) input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]):not([type="range"]):not([type="color"]),
:is(
  .form-group,
  .form-field,
  .data-form,
  .daily-report-form,
  .inline-form,
  .repeatable-row,
  .repeatable-row__field,
  .review-actions,
  .submitted-report__return-control,
  .purge-confirm__body,
  .user-manage-suspend-form,
  .assignment-revoke-form,
  .director-add-form
) select:not([hidden]),
:is(
  .form-group,
  .form-field,
  .data-form,
  .daily-report-form,
  .inline-form,
  .repeatable-row,
  .repeatable-row__field,
  .review-actions,
  .submitted-report__return-control,
  .purge-confirm__body,
  .user-manage-suspend-form,
  .assignment-revoke-form,
  .director-add-form
) textarea:not([hidden]),
.user-state-input {
  min-height: 48px;
  padding: 12px 14px;
  border: 1px solid var(--control-border);
  border-radius: var(--control-radius);
  background: var(--control-fill);
  color: var(--control-text);
  font-family: inherit;
  /* 16px exactly, never a rem — this page family shrinks the root font on
     narrow screens, and inheriting that would put fields back under the iOS
     zoom threshold on precisely the smallest devices. */
  font-size: 16px;
  line-height: 1.5;
}

/* Width is deliberately never set here. Several fields are intentionally
   narrow (a 5rem number box, a 110px state code), and forcing 100% would
   break every inline row in the app. */

/* --------------------------------------------------------------------------
   Placeholders — 4.98:1 against the fill. Readable, but never mistakable for
   text the user actually typed.
   -------------------------------------------------------------------------- */
input::placeholder,
textarea::placeholder {
  color: var(--control-placeholder);
  opacity: 1; /* Firefox dims placeholders by default, which would undo the ratio. */
}

/* --------------------------------------------------------------------------
   Focus — the tint lifts to white so "this one is active" reads instantly,
   plus a ring that does not depend on the border colour alone.
   -------------------------------------------------------------------------- */
input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]):not([type="range"]):not([type="color"]):focus,
select:not([hidden]):focus,
textarea:not([hidden]):focus,
.user-state-input:focus {
  background: #ffffff;
  border-color: var(--control-focus-border);
  outline: none;
  box-shadow: 0 0 0 3px var(--control-focus-ring);
}

/* --------------------------------------------------------------------------
   Textareas need real height and a resize handle; the 48px above is a
   single-line minimum.
   -------------------------------------------------------------------------- */
textarea:not([hidden]),
:is(.form-group, .form-field, .data-form, .daily-report-form) textarea:not([hidden]) {
  min-height: 112px;
  line-height: 1.55;
  resize: vertical;
}

/* Short-by-design textareas keep their own height. These carry a real reason
   (a one-line note, a compact suspend reason) rather than being an oversight. */
.user-manage-suspend-form textarea:not([hidden]) {
  min-height: 72px;
}

/* --------------------------------------------------------------------------
   Read-only pickers
   Date, time and month fields are readonly text inputs that open a popup on
   tap. Rendering them with a text caret says "type here" about something you
   cannot type into, which reads as broken rather than as a button.
   -------------------------------------------------------------------------- */
.date-picker__input,
.time-picker__input,
.month-picker__input {
  cursor: pointer;
}

/* --------------------------------------------------------------------------
   Disabled — must still be identifiable as a field, just plainly inert.

   Specificity has to match the resting rule above, not merely exist: at a
   plain (0,1,1) this silently lost and disabled fields rendered identically
   to editable ones. Caught by measuring, not by reading.
   -------------------------------------------------------------------------- */
input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]):not([type="range"]):not([type="color"]):disabled,
select:not([hidden]):disabled,
textarea:not([hidden]):disabled {
  background: #f1f4f4;
  border-color: #b6c3c1;
  color: #6b7d82;
  cursor: not-allowed;
}

/* --------------------------------------------------------------------------
   Checkboxes, radios and file pickers

   These are a different control class and must never inherit the text-box.
   The rules above exclude them — but the PORTED reference stylesheet's own
   `.form-group input` does not, so a checkbox inside a .form-group is
   stretched to 100% width and 48px tall by a rule written for text fields.
   That trap has been hit before and was worked around once with a bespoke
   .checkbox-field class on one page; since this file is now the single place
   field styling is decided, it's cheaper to neutralise it here for good than
   to keep discovering it per page.

   Specificity is matched to the reference's `.form-group input` (0,2,0) and
   this file loads last, so source order settles it.
   -------------------------------------------------------------------------- */
:is(.form-group, .form-field, .data-form, .daily-report-form, .repeatable-row) input[type="checkbox"],
:is(.form-group, .form-field, .data-form, .daily-report-form, .repeatable-row) input[type="radio"] {
  display: inline-block;
  width: auto;
  min-height: 0;
  min-width: 0;
  margin: 0;
  padding: 0;
}

:is(.form-group, .form-field, .data-form, .daily-report-form, .repeatable-row) input[type="file"] {
  display: block;
  width: 100%;
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
}

/* =============================================================================
   SECONDARY BUTTON — 2026-08-09

   Employees reported not being able to tell the dashboard's "View projects"
   from the page background. Measured against the live stylesheet, they were
   right, and it was never a dashboard problem: .secondary-action is used 84
   times across 22 pages, 14 of them on the customer portal.

     control                       shape vs page   border vs page
     .secondary-action (before)         1.08:1          1.22:1
     .primary-action                    7.04:1            n/a

   WCAG 1.4.11 asks 3:1 for the boundary of an interface control. The two sat
   side by side with a 6.5x difference in how visible they were as shapes,
   while the label read fine at 11.8:1 — so people could read the words and
   still not register it as pressable.

   THE BORDER IS WHAT PASSES. The tint is not decoration-by-accident: measured,
   a navy fill alone reaches only 1.31:1 even at 15%, so it could never carry
   this on its own. It is there to separate the control from the teal primary
   at a glance. Navy at 60% measures 3.71:1 on a white card and 3.60:1 on the
   page ground — the two backgrounds these buttons actually sit on.

   There is exactly ONE global .secondary-action rule in the reference sheet
   (line ~694). Two others look global but are descendant selectors split
   across two lines (".daily-work-none-noted\n.secondary-action"), handled
   separately below.
   ============================================================================= */

.secondary-action {
  border-color: rgba(0, 60, 85, 0.6);
  background: rgba(0, 60, 85, 0.09);
}

/* The reference sheet's hover already goes to full navy, which is correct and
   stronger than the resting state — restated only so the tint deepens with it
   instead of snapping back to the reference's flat --surface-muted. */
.secondary-action:hover {
  border-color: var(--alder-navy, #003c55);
  background: rgba(0, 60, 85, 0.15);
}

/* The daily-report "Mark no work / no equipment noted" buttons. These override
   border-color themselves at (0,2,0), so a bare .secondary-action rule cannot
   reach them however late it loads — matched at the same specificity here.
   They keep TEAL rather than inheriting navy: it is the affirmative "nothing
   to report" action and reads as a success, not a neutral secondary.
   Measured, teal needs more alpha than navy to clear the bar — 60% reaches
   only 2.96:1 and just misses, so this uses 70% (3.69:1 on white,
   3.54:1 on the page). Their previous 30% measured 1.64:1. */
.daily-work-none-noted .secondary-action,
.daily-resource-none-noted .secondary-action {
  border-color: rgba(21, 94, 84, 0.7);
  background: rgba(21, 94, 84, 0.08);
}

/* =============================================================================
   GHOST BUTTON — 2026-08-09

   Same defect as .secondary-action above, same measured value, missed because
   the fix was applied to one class and not the other. `.btn-ghost` is the
   "Log out" button in BOTH header partials (staff and customer portal), in
   both the desktop bar and the mobile hamburger — so it is the single
   most-repeated control in the product, and it measured 1.29:1 as a shape
   against the white header. Its label reads at 15.38:1, which is why people
   could read the word "Log out" and still not perceive a button.

   Values are copied verbatim from .secondary-action rather than re-derived,
   because that pair is already measured and approved: 3.71:1 on a white
   surface, 3.60:1 on the page ground. The header sits on --color-surface
   (#ffffff), the white case.

   Colour only — .btn-ghost already declares `border: 1px solid var(--hairline)`,
   so the border WIDTH is untouched and no box dimension changes. This cannot
   reflow the header.
   ============================================================================= */
.btn-ghost {
  border-color: rgba(0, 60, 85, 0.6);
  background: rgba(0, 60, 85, 0.09);
}

/* style.css sets `:hover { background: var(--color-bg) }`, which is LIGHTER
   than the resting tint above — so without this the button would appear to
   recede on hover. Deepens instead, matching .secondary-action:hover. */
.btn-ghost:hover {
  border-color: var(--alder-navy, #003c55);
  background: rgba(0, 60, 85, 0.15);
}

/* =============================================================================
   FOCUS RING — 2026-08-09

   Verified on the live login page by walking document.styleSheets: ZERO
   :focus-visible rules matched .btn / .primary-action / .secondary-action.
   The reference stylesheet carries 21 designed focus rules, but the auth,
   legal, error and plain-admin pages don't load it, so those four page
   families fell back to the UA default ring while everything else got a
   designed one. Keyboard users got a different affordance depending on which
   page they were on.

   :focus-visible (not :focus) so a mouse press never paints a ring — that is
   what makes this safe to apply globally to buttons.

   `outline` rather than box-shadow: it follows border-radius, and it cannot
   participate in layout, so this adds no geometry on any of the ~200 buttons
   it now reaches. outline-offset: 2px keeps the ring clear of the button's
   own fill so it stays visible on the filled teal primary as well as on the
   outlined secondary.

   Navy is carried with a literal fallback because --alder-navy is defined in
   the REFERENCE sheet's :root, which four of the seven page families do not
   load — the same reason .secondary-action:hover above already uses a
   fallback. Without it the ring would silently vanish on exactly the pages
   this rule exists to fix.
   ============================================================================= */
.btn:focus-visible,
.primary-action:focus-visible,
.secondary-action:focus-visible,
.danger-action:focus-visible,
.link-button:focus-visible {
  outline: 3px solid var(--alder-navy, #003c55);
  outline-offset: 2px;
}

/* =============================================================================
   DISABLED CURSOR — 2026-08-09

   style.css sets `.btn[disabled] { cursor: wait }`, which is correct for the
   in-flight submit that form-loading.js creates but wrong for a control that
   is genuinely inert — and it contradicted `.btn-danger:disabled`, which
   already used not-allowed. Two disabled buttons on one page disagreed about
   what disabled means.

   The discriminator is form-loading.js's own contract: when it disables the
   pressed button it ALSO adds .is-submitting-target (see that file, and the
   spinner rule in style.css keyed on the same class). So
   :not(.is-submitting-target) is precisely "disabled, but not because a
   submit is running" — the tested submit feedback is left exactly as it was.

   Cursor only. No geometry, no colour, no effect on touch devices at all.
   ============================================================================= */
.btn[disabled]:not(.is-submitting-target),
.primary-action[disabled]:not(.is-submitting-target),
.secondary-action[disabled]:not(.is-submitting-target),
.danger-action[disabled]:not(.is-submitting-target) {
  cursor: not-allowed;
}

/* =============================================================================
   SPACING UTILITIES — 2026-08-09

   .mt-2 / .mt-3 / .mb-3 were defined SIX times, byte-identically (verified by
   hashing all six: f962c2c7b6c5), once in each of daily-report-overrides,
   dashboard-overrides, offline-draft-overrides, portal-overrides,
   projects-overrides and timesheet-overrides — and in no globally-loaded
   sheet. 100 usages across 15 view files depended on whichever copy the page
   happened to load.

   That is not a hypothetical fragility. It has already shipped as a bug twice:
   projects-overrides.css's own copy exists because show.ejs's mt-2/mt-3/mb-3
   were silently no-ops (see the comment that used to sit above it), and the
   same class of defect hit the portal's issue-record padding. A seventh page
   family added tomorrow without an override file would fail the same way.

   WHY THIS FILE AND NOT style.css

   style.css would be the natural home semantically — it already carries the
   global content primitives — but it loads FIRST, before
   daily-report-reference.css, and that would silently break these utilities
   rather than centralise them.

   Measured before moving anything. These competitors all sit at exactly
   (0,1,0), the same specificity as the utilities, so source order alone
   decides them:

     .form-notice                     margin-bottom: 26px    (reference)
     .submitted-report__history       margin: 0 0 22px       (reference)
     .submitted-report__review-panel  margin: 0 0 22px       (reference)
     .daily-report-submission         margin-top: 26px       (reference)
     .detail-card                     margin-bottom: 0       (3 override files)

   Each of those is paired with .mb-3 or .mt-3 in real markup. Today the
   utility wins every one of them (confirmed: .form-notice.mb-3 computes 24px,
   not 26px; .detail-card.mb-3 computes 24px, not 0). Moving them to style.css
   would flip all five, turning .mb-3 into a no-op on roughly eleven pages.

   fields.css is the only sheet loaded LAST on every page — an invariant
   tests/dom/fieldStyling.test.ts already asserts — so the utilities keep
   winning exactly the same competitors they win today. Behaviour-preserving by
   construction:
     - competitors above (0,1,0) won before and still win  (specificity > order)
     - competitors below (0,1,0) lost before and still lose
     - competitors at    (0,1,0) lost before and still lose (this file is last)

   The semantic tension is real and worth naming: this file's header describes
   it as the home for shared CONTROLS, and spacing utilities are not controls.
   They are here because load order is load-bearing and this is the only sheet
   with the right position. If a dedicated shared tier is ever created, it must
   be linked AFTER this file for these to keep working — moving them earlier is
   the one change that would silently break them.

   NAMING, DELIBERATELY UNCHANGED: .mt-2 maps to --space-3 and .mt-3 to
   --space-4, i.e. the names are off by one from the tokens. That is confusing
   and worth fixing, but renaming is a separate change touching 100 call sites;
   preserving the exact values here keeps this move a pure de-duplication.
   ============================================================================= */
.mt-2 {
  margin-top: var(--space-3);
}

.mt-3 {
  margin-top: var(--space-4);
}

.mb-3 {
  margin-bottom: var(--space-4);
}
