/* Django glue for the V05 shell (Tweak 019).
   Small app-specific adjustments that don't belong in the ported prototype CSS
   (wireframe.css / shell.css / mobile.css are kept as source-of-truth). */

/* F122 (WCAG 2.4.1 Bypass Blocks): visually-hidden (sr-only) utility — content is
   available to assistive tech but clipped from the visual layout. Used for the
   skip link (revealed on focus) and any sr-only headings. */
.visually-hidden:not(:focus):not(:focus-within) {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* F122: skip-to-content link — first focusable element in the body. Visually
   hidden until focused (keyboard Tab), then it appears in the top-left corner
   so a keyboard/AT user can jump straight past the sidebar nav to <main>.
   Hidden via clip-path, NOT an off-canvas translate — anything parked above
   the viewport top gets revealed by overscroll rubber-banding. */
.skip-link {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: var(--z-skip-link);
  clip-path: inset(50%);
  /* a11y-visual-02: white 13px label needs >=4.5:1 — green-700 CTA fill (5.64:1). */
  background: var(--cta-bg);
  color: #fff;
  font-weight: 700;
  font-size: 13px;
  padding: 10px 16px;
  /* controller-role mobile pass: the 44px tap-target floor applies to every
     focusable element, including this one once it springs into view. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  border-radius: var(--radius-md);
  text-decoration: none;
  box-shadow: var(--shadow-md);
}
.skip-link:focus {
  clip-path: none;
  outline: 2px solid var(--enroute-green-50);
  outline-offset: 2px;
  box-shadow: var(--focus-ring), var(--shadow-md);
}
/* The <main> landmark is a script-focus target (tabindex=-1) so jumping to it
   doesn't draw a persistent focus ring around the whole content region. */
#main:focus { outline: none; }

/* Anonymous pages (login, password reset, error pages) have no sidebar, so the
   .hf-shell 2-column grid collapses to a single full-width column. */
.hf-shell--anon { grid-template-columns: 1fr; }

/* F023: generic in-content hyperlinks read in the BRAND link colour (--link,
   green-700), not the ad-hoc cobalt some page CSS still uses. Scoped to
   class-less links inside the main content region so it touches ONLY generic
   prose links — every chrome/nav link (.hf-*), button (.btn), status pill,
   role pill and page-CSS-classed link carries its own class and is untouched,
   so neither status nor nav colour is recoloured. Underline-on-hover keeps the
   affordance without shouting at rest. */
.hf-main a:not([class]) {
  color: var(--link);
  text-decoration: none;
}
.hf-main a:not([class]):hover {
  color: var(--link-hover);
  text-decoration: underline;
}
.hf-main a:not([class]):focus-visible {
  outline: none;
  border-radius: var(--radius-xs);
  box-shadow: var(--focus-ring);
}

/* The brand link is an interactive tap target — give it a >=44px hit area
   (ADR 0005), vertically centred within the 55px header. min-width is a no-op
   visually (the wordmark text is always far wider than 44px) but the ADR asks
   for both axes DECLARED, not merely true by accident of content — guarded by
   tests/conformance/test_app_shell_tap_floor.py. */
.hf-header__title { display: inline-flex; align-items: center; min-width: 44px; min-height: 44px; }

/* Sign-out must be a POST form (CSRF), rendered as a button but styled as the
   sidebar's signout link. Reset only the button chrome; colour/hover come from
   .hf-side__signout in shell.css. */
button.hf-side__signout {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  cursor: pointer;
  text-align: left;
}
.hf-side__account form { margin: 0; padding: 0; }

/* Form field/banner error styling — the V05 prototypes omitted error states, but
   Django forms need them. Shared by all re-skinned form pages (Tweaks 020+). */
.fld__err { color: var(--enroute-rose-700); font-size: 12px; line-height: 1.4; margin-top: 4px; }
/* The rose ring, restated at the specificity the typed control selectors reach.
   `.fld [aria-invalid="true"]` alone is (0,2,0); the base control rule in
   shell.css lists `.fld input[type="text"]`, `[type="number"]`, … each of
   which is (0,2,1) and supplies the ordinary grey hairline. So for EVERY typed
   native input the grey ring won and this was dead CSS — only `.fld textarea`
   and `.fld select` (both (0,1,1)) ever showed the error. The bare descendant
   selector stays last: it is what reaches a non-native control such as the
   `.cs__btn` that custom-select.js renders in a hidden select's place. */
.fld input[aria-invalid="true"],
.fld select[aria-invalid="true"],
.fld textarea[aria-invalid="true"],
.fld [aria-invalid="true"] { box-shadow: inset 0 0 0 1.5px var(--enroute-rose-500); }
/* `box-shadow` is ONE property, not a stack of layers, so the rule above at
   (0,2,1) would EVICT the a11y-code-14 focus ring (`.fld input:focus`,
   shell.css, also (0,2,1) in an earlier sheet) — a WCAG 2.4.7 loss on
   precisely the fields the user is being asked to correct. The
   focused-and-invalid state therefore restates both rings itself. */
.fld input[aria-invalid="true"]:focus,
.fld select[aria-invalid="true"]:focus,
.fld textarea[aria-invalid="true"]:focus,
.fld [aria-invalid="true"]:focus { box-shadow: inset 0 0 0 1.5px var(--enroute-rose-500), var(--focus-ring); }
.form-banner--error {
  background: var(--enroute-rose-50);
  color: var(--enroute-rose-700);
  border-radius: 10px;
  padding: 12px 14px;
  box-shadow: inset 0 0 0 1px var(--enroute-rose-200);
  font-size: 13px;
  margin-bottom: 16px;
}
.form-banner--error p { margin: 0; }
.form-banner--error p + p { margin-top: 4px; }
.form-banner--success {
  background: var(--enroute-green-50);
  color: var(--enroute-green-700);
  border-radius: 10px;
  padding: 12px 14px;
  box-shadow: inset 0 0 0 1px var(--enroute-green-200);
  font-size: 13px;
  margin-bottom: 16px;
}
.form-banner--info {
  background: var(--enroute-ink-50);
  color: var(--enroute-ink-700);
  border-radius: 10px;
  padding: 12px 14px;
  box-shadow: inset 0 0 0 1px var(--enroute-ink-200);
  font-size: 13px;
  margin-bottom: 16px;
}
/* release-management (#24): a small, non-blocking gantry-mismatch badge on a
   release_note row (load detail + document centre). A DEDICATED class — NOT
   .st-pill (the conformance-pinned status vocabulary). Amber = advisory; the
   full reason is the title= tooltip. */
.od-doc__badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: var(--radius-pill);
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  vertical-align: middle;
  white-space: nowrap;
  background: var(--enroute-amber-100);
  color: var(--enroute-amber-800);
  cursor: help;
}
/* emergency badge (#31 SP5): the dedicated "Urgent" marker, modeled on
   .od-doc__badge — a DEDICATED class, NOT an .st-pill / s-* status modifier
   (urgency and lifecycle are orthogonal). A SIBLING of the status pill on every
   surface (list, work-queue, detail header, dashboard). Rose tokens (the
   higher-severity family; amber is taken by the gantry-mismatch advisory). */
.od-emergency-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: var(--radius-pill);
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  vertical-align: middle;
  white-space: nowrap;
  background: var(--emergency-bg);
  color: var(--emergency-fg);
  cursor: help;
}
/* #32 missing-file-identification: the internal order-list "missing required
   documents" icon — a DEDICATED class (NOT an s-* status modifier), a SIBLING
   rendered immediately BEFORE the .st-pill (mobile.js clones it alongside the
   pill). Severity-graded by count of missing docs: --warn (1) / --mid (2) /
   --danger (3+). Light-tint chip + dark-ink glyph (the .od-emergency-badge
   model) so the hue is distinguishable AND the 15px glyph clears WCAG 1.4.11
   (3:1, non-text graphic). The tooltip/aria-label names the missing docs. */
.od-missing-docs {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin-right: 6px;
  border-radius: var(--r-sm);
  vertical-align: middle;
  cursor: help;
}
.od-missing-docs svg { display: block; }
.od-missing-docs--warn   { background: var(--missing-docs-warn-bg);   color: var(--missing-docs-warn-fg); }
.od-missing-docs--mid    { background: var(--missing-docs-mid-bg);    color: var(--missing-docs-mid-fg); }
.od-missing-docs--danger { background: var(--missing-docs-danger-bg); color: var(--missing-docs-danger-fg); }

/* finance-tracking (§6): internal order-list per-row finance status dots —
   client invoice paid · supplier invoice captured in Xero · supplier invoice
   paid. A FILLED green dot = done; a HOLLOW muted ring = pending. Shape + fill
   differ (not colour alone), so the state is not conveyed by colour only
   (WCAG 1.4.1); each dot also carries its own `title`, and the cluster an
   aria-label naming all three states. */
.fin-dots {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-left: 6px;
  vertical-align: middle;
}
.fin-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  box-sizing: border-box;
}
.fin-dot--on  { background: var(--fin-dot-on); }
.fin-dot--off { background: transparent; border: 1.5px solid var(--fin-dot-off); }
/* document-soft-delete (#25): a rose "Deleted by … on …" badge (prose, not the
   uppercase advisory pill) + the greyed row treatment, shown only to internal +
   the deleting org (the view already filtered non-retained users out). */
.od-doc__badge--deleted {
  background: var(--enroute-rose-100);
  color: var(--enroute-rose-700);
  text-transform: none;
  letter-spacing: 0;
  cursor: default;
}
/* The "Deleted by … on …" detail is a wrapping meta line (compact pill stays
   "Deleted"); rose-tinted so it reads as the deletion record. */
.od-doc__meta--deleted,
.m-ol-card__meta--deleted {
  color: var(--enroute-rose-600);
}
.od-doc--deleted,
.dc-row--deleted,
.m-ol-card--deleted {
  opacity: 0.62;
}
.od-doc--deleted .od-doc__icon {
  background: var(--enroute-rose-100);
  color: var(--enroute-rose-700);
}
/* Row-action icon buttons (edit pencil / delete trash) + the inline restore form.
   The icons open the page-level delete/edit modals (document-actions.js). */
.od-act {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--border-1);
  border-radius: var(--r-sm);
  background: var(--bg-paper);
  color: var(--fg-3);
  cursor: pointer;
  vertical-align: middle;
}
.od-act svg { width: 15px; height: 15px; }
.od-act:hover { background: var(--bg-canvas); color: var(--fg-1); }
.od-act--delete:hover {
  color: var(--enroute-rose-700);
  border-color: var(--enroute-rose-400);
}
.od-act--edit:hover { color: var(--enroute-cobalt-600, var(--fg-1)); }
.od-act__restore { display: inline-block; }
/* ---- ADR 0005: the 44x44 tap floor, on touch and nowhere else ----
   `.od-act` is drawn at desktop density (30x30) — fine for a mouse, below the
   floor on a phone, and it now sits right beside the 44x44 Download/Upload
   links order-detail.css raises at this same breakpoint. `.od-act` already
   carries display:inline-flex + centring above, at every viewport, so touch
   only needs to raise the floor: CSS resolves used width/height as the larger
   of the declared value and min-width/min-height, so this does not disturb
   the 30px desktop box. Same desktop/touch split order-detail.css's
   `.od-copy`/`.od-rowedit`/`.od-copy-addr` use. Guarded by
   tests/conformance/test_app_shell_tap_floor.py. */
/* ADR 0079 (2026-09-18): gated on the input device as well as the width — a
   finger on a tablet or a landscape phone gets the same floor a portrait phone
   does. Widened in place; the width arm is retained so a narrow mouse window is
   unchanged. */
@media (max-width: 640px), (hover: none) and (pointer: coarse) {
  .od-act { min-width: 44px; min-height: 44px; }
}

/* Document delete/edit modal bodies (the .enr-modal shell is modal.css). */
.od-modal__note { font-size: 13px; color: var(--fg-2); margin: 0 0 var(--space-3); }
.od-modal__field {
  display: block;
  font-size: 13px;
  color: var(--fg-2);
}
.od-modal__field input {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border-1);
  background: var(--bg-paper);
  color: var(--fg-1);
}
/* status-rework #22: load-detail status-advance controls + the Delivering X/Y rollup */
.detail-statusbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.detail-rollup { color: var(--enroute-ink-500); font-weight: 500; }

/* Django messages banner (V05 — replaces the legacy .app-banner at cutover). */
.msg-stack { max-width: 640px; margin: 0 auto 20px; display: flex; flex-direction: column; gap: 8px; }
/* `display: flex` above outranks the user-agent `[hidden] { display: none }` rule, so
   a `.msg-stack` marked `hidden` renders in FULL without this line — the trap recorded
   in conventions.md "[hidden] is a USER-AGENT rule". The stack is server-rendered from
   the messages framework (always populated when present), but ADR 0066 pages carry a
   client-revealed copy for a failed document read, and that one starts hidden. */
.msg-stack[hidden] { display: none; }
/* The failed-document banner lays its sentence and its Reload control on one row, and
   wraps to two on a narrow phone rather than squeezing the control below the floor.
   The `flex: 1 1 auto; min-width: 0` on the sentence is what makes that true: without it
   the span takes its default `flex: 0 1 auto` with no min-width, so at the stack's 640px
   cap the sentence claimed the whole line and the button wrapped to a second row at EVERY
   width — measured at 1440, 1280, 1024, 768, 430 and 375 — leaving `justify-content`
   inert and this comment describing a layout that never rendered. */
.msg-banner--doc { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); justify-content: space-between; }
.msg-banner--doc > span { flex: 1 1 auto; min-width: 0; }
.msg-banner { border-radius: 10px; padding: 12px 14px; font-size: 13px; line-height: 1.45; box-shadow: inset 0 0 0 1px var(--border-1); }
.msg-banner--error { background: var(--enroute-rose-50); color: var(--enroute-rose-700); box-shadow: inset 0 0 0 1px var(--enroute-rose-200); }
.msg-banner--warning { background: var(--enroute-amber-50); color: var(--enroute-amber-800); box-shadow: inset 0 0 0 1px var(--enroute-amber-200); }
.msg-banner--success { background: var(--enroute-green-50); color: var(--enroute-green-800); box-shadow: inset 0 0 0 1px var(--enroute-green-200); }
.msg-banner--info { background: var(--enroute-ink-50); color: var(--fg-2); }

/* frontend primitives (front-end-framework SP4b/SP5, architecture §3: this
   sheet gains a dedicated section for the rules that have no home today).
   New canonical vocabulary with no pre-existing home — `.btn`, `.st-pill`,
   `.fld`, `.form-intro`, `.surface` stay where they already are (moving a
   live rule is the cascade risk the spec's "pin exact relative order"
   forbids). Every value below is either an already-declared token or a
   raw pixel figure copied verbatim from the closest existing sibling
   pattern named in each rule's comment — no new raw value is invented. */

/* `.btn--icon`: a square icon-only `.btn`. Every other `.btn--*` variant's
   background/colour/hover stays exactly as declared above — this modifier
   only removes the label's padding/gap and fixes the width to the shared
   `min-height: 36px`, matching the 28px page-local precedent's shape
   (`.ftr-iconbtn`, fuel-terms.css) at this primitive's own already-declared
   size. */
.btn--icon {
  width: 36px;
  padding: 0;
  justify-content: center;
}

/* `.icon`/`.icon--sm`/`.icon--lg`: the registry's three size/stroke-width
   pairings (architecture §4) — the three most common existing pairings
   the inventory found: modal close-X (14px/1.6, `.enr-modal__close svg`'s
   authored size before its own page CSS override), nav item (16px/1.75,
   `partials/_nav_icon.html`'s authored stroke-width), empty-state glyph
   (18px/1.7, `order_list/_order_results.html`'s `.olr-empty__ico` svg,
   copied verbatim). `_icon.html` sets no `stroke-width` attribute, so it
   must come from CSS, not the markup. */
.icon {
  width: 16px;
  height: 16px;
  stroke-width: 1.75;
  flex-shrink: 0;
}
.icon--sm { width: 14px; height: 14px; stroke-width: 1.6; }
.icon--lg { width: 18px; height: 18px; stroke-width: 1.7; }

/* `.empty-state*`: dimensions and colours copied verbatim from the
   established page-local precedent this primitive replaces
   (`.olr-empty`/`.olr-empty__ico`/`.olr-empty__t`/`.olr-empty__s`,
   orders-list.css:1391-1411) — same padding, gap, icon-badge size and
   tokens, renamed to the shared family. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
  padding: 56px 20px;
}
.empty-state__icon {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--bg-sunken);
  box-shadow: inset 0 0 0 1px var(--border-1);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--fg-3);
}
.empty-state__title { margin: 0; font-size: 14px; font-weight: 700; color: var(--fg-1); }
.empty-state__message { margin: 0; font-size: 12.5px; color: var(--fg-3); }
.empty-state__action { margin-top: 4px; }

/* `.form-banner__icon/__title/__body`: additive sub-elements inside the
   existing `.form-banner--*` tone rules above — none of THOSE base rules
   are touched, so every page that already renders a bare `.form-banner--*`
   with raw `<p>` children (the golden-captured usage) is byte-for-byte
   unaffected. New vocabulary, screenshot-gated per page as it migrates
   (goldens/MANIFEST.md). */
.form-banner__icon { display: inline-flex; vertical-align: -3px; margin-right: 6px; }
.form-banner__title { margin: 0 0 2px; font-size: 13px; font-weight: 700; }
.form-banner__body { margin: 0; }

/* `.form-banner--layout-*`: frontend-overlays (F2) SP1 follow-up, exception
   X7's remaining `class` gap (JJ ruling, 2026-09-14 — 04-build-log.md SP1).
   Promotes two page-owned layout rules from `loads-placement.css` into the
   shared banner as a `layout=` option on `ui_notice`/`ui_notice_block`
   (`frontend/tags/notice.py`) instead of relaxing the no-page-`class` rule
   (`tags/attrs.py` still rejects it). `_notice.html` emits the class only
   for a non-default `layout`, so a bare `.form-banner--*` is unaffected.
   Both source rules used raw `12px`/`4px`; replaced with the token scale's
   exact matches, `--space-3` (12px) and `--space-1` (4px) (`tokens.css`).
   `--layout-trailing-action` was `.lp-reorder-banner` (the reorder-prefill
   advisory's message-left/dismiss-right row); `--layout-stacked` was
   `.lp-declined-banner` (the declined-request banner's stacked heading +
   reason), including its `strong{font-weight:700}` rule. */
/* The rules select the BODY SLOT, not the outer element: `_notice.html` wraps
   the block tag's content in `.form-banner__body`, so the outer element has
   exactly one child and a flex/grid on it lays out nothing (first cut had the
   dismiss button flush against the message — caught by the screenshot pass,
   pinned by tests/conformance/frontend-contract/test_notice_layout_css.py). */
.form-banner--layout-trailing-action > .form-banner__body {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.form-banner--layout-stacked > .form-banner__body {
  display: grid;
  gap: var(--space-1);
}
.form-banner--layout-stacked strong { font-weight: 700; }

/* `.form-banner--warning`: absent until now. Same structure as the
   `--error`/`--success`/`--info` trio above (10px radius, 12px/14px pad,
   13px text, 16px bottom margin); its amber-50/amber-800/amber-200 triad
   is copied verbatim from this file's own `.msg-banner--warning` two rules
   up — the tree's one other already-shipped amber banner, not a new
   pairing (the ramp has no `amber-700` to match the other three tones'
   "-700" foreground step, so amber-800 is what the existing sibling
   already uses). */
.form-banner--warning {
  background: var(--enroute-amber-50);
  color: var(--enroute-amber-800);
  border-radius: 10px;
  padding: 12px 14px;
  box-shadow: inset 0 0 0 1px var(--enroute-amber-200);
  font-size: 13px;
  margin-bottom: 16px;
}

/* `.frontend-reference`: component_reference.html's own page-root scope
   class — not a primitive, and not touching any primitive's rule. Every
   real page loads wireframe.css from base.html, whose `* { box-sizing:
   border-box; }` reset is the only thing that keeps `.fld`'s control rule
   (shell.css: `width: 100%` + `padding: 10px 12px`, no `box-sizing` of
   its own) sized inside its container — the reference page deliberately
   skips wireframe.css (framework-tier assets only) and so skips that reset
   too, and its bare `<input>`/`<textarea>` widened past their `.surface`
   by exactly their own horizontal padding (page overflowed 1440px by
   10px). Restated here, scoped to this page's own root class so no other
   page's computed style is touched (every page that loads wireframe.css
   already computes border-box for these elements — this rule changes
   nothing there). */
.frontend-reference,
.frontend-reference * {
  box-sizing: border-box;
}
