/* ========================================================
   UTILITY CLASSES — APPEARANCE LAYER
   ========================================================
   OWNERSHIP (see IMPROVEMENT_PROPOSAL.md §1.2):
     grid.css        owns LAYOUT   — display, flex, grid, gap, width/height,
                                     position, overflow, spans, containers.
     utilities.css   owns APPEARANCE — colours, borders, radius, badges,
                                     cursor, plus the margin/padding steps
                                     that grid.css does not define.
     typography.css  owns TEXT     — size, weight, muted/primary/error colours.

   Anything previously duplicated here AND in grid.css has been removed:
   utilities.css is linked BEFORE grid.css in base.html, so grid.css was
   already winning every one of those declarations. Deleting the losing
   copies is a no-op at render time and removes the ambiguity.

   Three declarations below intentionally keep `!important` — they are
   load-bearing. `.hidden` in particular must beat `.form-group{display:flex}`
   from components/forms.css, which is linked LATER than grid.css. Without
   the `!important`, every conditional "Other (Specify)" field in the data
   entry forms would be permanently visible.
   ======================================================== */

/* ========================================================
   ACCESSIBILITY HELPERS
   ======================================================== */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0;
}
.sr-only-focusable:focus {
  position: static; width: auto; height: auto;
  padding: inherit; margin: inherit; overflow: visible;
  clip: auto; white-space: normal;
}

/* The skip link needs to render ON TOP of the fixed sidebar and mobile
   header when focused, otherwise it appears underneath them and the
   keyboard user sees nothing happen. */
.skip-link:focus {
  position: fixed;
  top: var(--space-2, 8px);
  left: var(--space-2, 8px);
  z-index: var(--z-skip-link, 1100);   /* above the sidebar and mobile header */
  padding: 12px 20px;
  background: var(--brand);
  color: var(--on-brand);
  border-radius: var(--radius-sm);
  box-shadow: var(--elev-3);
  text-decoration: none;
  font-weight: 600;
}

/* ========================================================
   TOUCH TARGETS
   ========================================================
   Previously this was a bare `.tap-target, button, a, input, .btn { ... }`
   rule, which applied `padding: 0.5rem 1rem` to EVERY anchor and input in
   the application — inline links, sidebar nav items, DataTables row
   checkboxes, the dashboard month picker. It also set `min-height:16px`,
   which is far below any touch-target guideline despite the comment above
   it claiming otherwise.

   Now scoped to genuinely interactive controls, at the WCAG 2.5.5 /
   iOS HIG minimum of 44px. Padding is left to the component that owns
   the element (buttons.css, main.css) rather than imposed globally.
   ======================================================== */
.tap-target,
button,
.btn,
[role="button"] {
  min-height: 44px;
  min-width: 44px;
  touch-action: manipulation;
}

/* Checkboxes and radios must never be inflated by touch-target sizing —
   they are sized explicitly by the component that renders them. */
input[type="checkbox"],
input[type="radio"] {
  min-height: 0;
  min-width: 0;
  padding: 0;
  touch-action: manipulation;
}

/* Common action classes get a slightly larger target than the 44px floor. */
.btn-action, .btn-danger, .btn-edit, .btn-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0.5rem 1rem;
}

/* ========================================================
   TEXT ALIGNMENT
   (not defined in grid.css, so it lives here)
   ======================================================== */
.text-center { text-align: center; }
.text-left   { text-align: left; }
.text-right  { text-align: right; }

/* ========================================================
   SPACING STEPS NOT PROVIDED BY grid.css
   grid.css defines .m-0..4, .p-0..6, .px-*, .py-*, .mx-*, .my-*.
   The directional single-axis steps below are used throughout the
   templates and exist only here.
   ======================================================== */
.m-0 { margin: 0 !important; }   /* load-bearing: beats heading margins */
.p-0 { padding: 0 !important; }  /* load-bearing: beats component padding */

.mt-1 { margin-top: 0.25rem; }
.mt-2 { margin-top: 0.5rem; }
.mt-4 { margin-top: 1rem; }
.mt-6 { margin-top: 1.5rem; }
.mt-auto { margin-top: auto; }

.mb-1 { margin-bottom: 0.25rem; }
.mb-3 { margin-bottom: 0.75rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-5 { margin-bottom: 1.25rem; }
.mb-6 { margin-bottom: 1.5rem; }
.mb-8 { margin-bottom: 2rem; }

.ml-0 { margin-left: 0; }
.ml-1 { margin-left: 0.25rem; }
.ml-2 { margin-left: 0.5rem; }
.ml-auto { margin-left: auto; }   /* push an item to the end of a flex row */
.mr-2 { margin-right: 0.5rem; }

.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
.pt-4 { padding-top: 1rem; }
.pb-2 { padding-bottom: 0.5rem; }
.pb-3 { padding-bottom: 0.75rem; }
.py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; }

.font-normal { font-weight: 400; }
.whitespace-nowrap, .nowrap { white-space: nowrap; }
.table-auto { table-layout: auto; }

/* ========================================================
   DISPLAY
   ======================================================== */
/* Load-bearing `!important`: must beat components/forms.css
   `.form-group { display: flex }`, which is linked after grid.css. */
.hidden { display: none !important; }

/* ========================================================
   SIZING (fixed steps used by chart/panel wrappers)
   ======================================================== */
.h-300 { height: 300px; }
.h-fit { height: fit-content; }
.max-w-600 { max-width: 600px; }
.max-h-300 { max-height: 300px; }

/* ========================================================
   BORDERS & LISTS
   ======================================================== */
.border-none { border: none; }
.list-none { list-style: none; }
.uppercase { text-transform: uppercase; letter-spacing: 0.04em; }

/* ========================================================
   COLOURS

   `.bg-success` / `.bg-warning` / `.bg-danger` and `.text-white` are GONE.
   The comment that used to sit here said they were "applied by
   static/js/main.js — do not remove as unused", and both halves of that had
   stopped being true: main.js was split into five files in v1.15.0, and the
   one surviving caller (the sales status badge in main.tables.js) was
   hand-building a badge out of `bg-success text-white px-2 py-1 rounded
   text-xs` instead of using .badge-positive / .badge-caution. It uses the
   component now, which leaves these four with no callers anywhere.

   `.text-white` is the one worth naming: a literal #ffffff is exactly what
   CLAUDE.md's "--on-brand, never #fff" rule forbids, because the status
   fills lighten in dark mode and a white label on them lands at about 2:1.
   Leaving the class in the sheet leaves that mistake one autocomplete away.
   ======================================================== */
.bg-transparent  { background: transparent; }
.bg-light-shade  { background: rgba(0, 0, 0, 0.05); }

.text-orange { color: var(--accent-orange-ink, var(--accent-orange)); }
.text-red    { color: var(--accent-red-ink, var(--accent-red)); }
/* .text-blue is a legacy name kept because it is applied in several
   templates. It now paints the BRAND colour, which is orange — renaming it
   across the templates would put unrelated churn in a colour-only change.
   Use .text-brand in new markup. */
.text-blue   { color: var(--brand-ink); }
.text-brand  { color: var(--brand-ink); }
.text-green  { color: var(--accent-green-ink, var(--accent-green)); }
/* Semantic INK aliases. Prefer these in new markup: they say what the colour
   MEANS rather than which hue it happens to be, and they are the pairing
   CLAUDE.md's colour rules describe — the -ink tokens are for text, the bare
   --positive / --negative tokens are fills. */
.text-positive { color: var(--positive-ink); }
.text-negative { color: var(--negative-ink); }
.text-caution  { color: var(--caution-ink); }
.text-main   { color: var(--text-main); }

/* ========================================================
   BADGE
   ======================================================== */
.badge {
  display: inline-flex;
  align-items: center;
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* ========================================================
   SMALL LAYOUT PATTERNS
   These class names were already used in the templates but had never been
   defined anywhere, so they were inert.
   ======================================================== */

/* The heading block at the top of every page. */
.page-title h1,
.page-title h2 { margin: 0 0 var(--space-1) 0; }
.page-title p  { margin: 0; color: var(--text-muted); }

/* Button row at the foot of a form. Wraps rather than overflowing on
   narrow screens, and right-aligns from tablet up. */
.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
}
@media (min-width: 640px) {
  .form-actions { justify-content: flex-end; }
}

/* ========================================================
   CSP REPLACEMENTS (v2.0.0, §5.3)
   ========================================================
   Everything below exists because `style-src` no longer allows
   'unsafe-inline'. A style="" attribute in markup is an inline style,
   and — unlike a <script> — there is no nonce for an attribute, so the
   only way to keep the strict directive was to give every one of the 56
   inline declarations in templates/ a class to live in.

   This is NOT busywork disguised as security. It is the same rule the
   rest of this stylesheet already followed and the templates quietly did
   not: a font size or a background written into the markup is invisible
   to the dark-theme audit, cannot be changed in one place, and was where
   most of the remaining literal values in this codebase were hiding.

   NOTE for JavaScript: CSP governs MARKUP. `el.style.width = x` from a
   script is CSSOM and is not blocked, which is why DataTables, Chart.js
   and jQuery's .css() all keep working untouched under the strict
   policy. Only HTML strings — including ones built in JS and assigned
   through innerHTML — need these classes.
   ======================================================== */

/* --- Type scale for dense table cells and chart captions ---------- */
.fs-10 { font-size: 10px; }
.fs-11 { font-size: 11px; }
.fs-12 { font-size: 12px; }
.fs-13 { font-size: 13px; }
.fs-15 { font-size: 15px; }

/* --- Surfaces, reused by the dashboard panels -------------------- */
.bg-input   { background: var(--input-bg); }
.bg-card    { background: var(--card-bg-solid); }
.bg-negative-wash { background: rgba(var(--accent-red-rgb), 0.12); }

/* --- Badges. Replaces five hand-written inline variants; the colour
       pairings are the INK tokens, never the fill tokens, because these
       are text on a wash rather than text on a fill. --caution as plain
       text is 2.3:1. ------------------------------------------------ */
.badge-positive { background: rgba(var(--accent-green-rgb), 0.12);  color: var(--positive-ink); padding: 4px 8px; border-radius: var(--radius-sm); }
.badge-caution  { background: rgba(var(--accent-orange-rgb), 0.16); color: var(--caution-ink);  padding: 4px 8px; border-radius: var(--radius-sm); }
.badge-negative { background: rgba(var(--accent-red-rgb), 0.12);    color: var(--negative-ink); padding: 4px 8px; border-radius: var(--radius-sm); }
.badge-neutral  { border: 1px solid var(--border); color: var(--text-muted); padding: 4px 8px; border-radius: var(--radius-sm); }
.badge-brand    { background: var(--brand-wash-strong);            color: var(--brand-ink);    padding: 4px 8px; border-radius: var(--radius-sm); }
.badge-plain    { background: var(--input-bg);                     color: var(--text-main);    padding: 4px 8px; border-radius: var(--radius-sm); }

/* THE UPPERCASE VARIANT (v2.8.0). Two more badge systems used to exist for
   the sake of this one difference:

     .status-badge + .status-pending/-progress/-done   dashboard.html
     .badge-action-INSERT/-UPDATE/-DELETE              admin_audit.html

   Both were `.badge` with a shorter font size, upper case and — in the
   status one — a pill radius, and both re-declared the same three
   wash/ink pairings the variants above already hold. They are one class now.

   `--radius-full`, not --radius-lg: at 11px with 4px of vertical padding the
   chip is ~19px tall and --radius-lg's 20px already rounds it to a capsule,
   so the token that MEANS capsule is the honest one to name. */
.badge--upper {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 4px 12px;
    border-radius: var(--radius-full);
}

/* --- The pill the dashboard date filter sits in ------------------- */
.filter-pill {
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: var(--radius-md);
  padding: 2px 12px;
}
.filter-pill-sm {
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: var(--radius-sm);
  padding: 2px 8px;
}
/* A <select> that should read as plain text inside a pill. */
.select-bare {
  border: none;
  background: transparent;
  font-weight: 600;
  outline: none;
  color: var(--text-main);
  font-family: inherit;
  font-size: 13px;
}
.select-compact { padding: 2px 4px; min-width: 120px; }
.pill-btn { padding: 8px 14px; border-radius: var(--radius-md); }

/* --- A button that has to look like a link ------------------------ */
.btn-linklike {
  background: none;
  border: none;
  cursor: pointer;
  font: inherit;
  padding: 0;
}
.btn-linklike-negative { color: var(--negative-ink); }
.btn-linklike-brand    { color: var(--brand-ink); }
/* Icon-only action buttons rendered by main.tables.js into DataTables
   cells. Referenced only from JavaScript — do not remove as "unused". */
.icon-action { background: none; border: none; cursor: pointer; padding: 0; }
.icon-action-edit    { color: var(--accent-blue); }
.icon-action-approve { color: var(--accent-green); }

/* --- Truncation for the device-info column ------------------------ */
.truncate-250 {
  max-width: 250px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- Tables --------------------------------------------------------- */
.table-collapse { border-collapse: collapse; }
.col-30 { width: 30%; }

/* --- The brand plate, at the five sizes it is actually used at ----- */
/* partials/_brand.html took a pixel size as a Jinja variable and wrote it
   into style="". These are every value passed as `brand_size` today:
   mobile header 30, sidebar 36, the 40 default, error pages 48, login 72.

   A size with no class here silently falls back to .brand-mark's 40px
   rather than rendering wrong — the partial has a comment saying so, next
   to the one line that would need changing. */
.brand-mark--30 { width: 30px; height: 30px; }
.brand-mark--36 { width: 36px; height: 36px; }
.brand-mark--40 { width: 40px; height: 40px; }
.brand-mark--48 { width: 48px; height: 48px; }
.brand-mark--72 { width: 72px; height: 72px; }

/* --- Error-page accent ------------------------------------------- */
/* errors/_error_base.html interpolated a colour into style="color: …".
   Four error pages, three accents. */
.error-accent-caution  { color: var(--caution-ink); }   /* 403, 413 */
.error-accent-negative { color: var(--negative-ink); }  /* 500 */
.error-accent-brand    { color: var(--brand-ink); }     /* 404 */

/* --- Percentage widths for progress bars -------------------------- */
/* THE ONE THAT LOOKS SILLY AND IS NOT.

   A progress bar width is a computed number, and a computed number in
   markup means style="width: 63.4%" — the exact thing the strict
   style-src forbids, and the only category of inline style that cannot
   simply be moved to a named class.

   The three alternatives were all worse. Setting the width from
   JavaScript works under CSP but renders every bar at 0% with scripts
   off, and this codebase deliberately keeps working without them.
   A <style nonce> block per page puts generated CSS in the document on
   every request. Dropping the bars is not an option — the target cards
   are the dashboard.

   So: 101 classes, one per whole percent, ~4 KB before gzip and closer
   to 400 bytes after (they compress almost perfectly). The template
   rounds to the nearest integer, which on a bar 200px wide is a
   difference of two pixels.

   Generated by hand once and checked in, NOT built by a tool: a
   generator would be a fourth build step to remember (tools/build_css.py
   already has to be re-run after any edit here) for output that will
   never change. */
.w-pct-0 { width: 0%; }
.w-pct-1 { width: 1%; }
.w-pct-2 { width: 2%; }
.w-pct-3 { width: 3%; }
.w-pct-4 { width: 4%; }
.w-pct-5 { width: 5%; }
.w-pct-6 { width: 6%; }
.w-pct-7 { width: 7%; }
.w-pct-8 { width: 8%; }
.w-pct-9 { width: 9%; }
.w-pct-10 { width: 10%; }
.w-pct-11 { width: 11%; }
.w-pct-12 { width: 12%; }
.w-pct-13 { width: 13%; }
.w-pct-14 { width: 14%; }
.w-pct-15 { width: 15%; }
.w-pct-16 { width: 16%; }
.w-pct-17 { width: 17%; }
.w-pct-18 { width: 18%; }
.w-pct-19 { width: 19%; }
.w-pct-20 { width: 20%; }
.w-pct-21 { width: 21%; }
.w-pct-22 { width: 22%; }
.w-pct-23 { width: 23%; }
.w-pct-24 { width: 24%; }
.w-pct-25 { width: 25%; }
.w-pct-26 { width: 26%; }
.w-pct-27 { width: 27%; }
.w-pct-28 { width: 28%; }
.w-pct-29 { width: 29%; }
.w-pct-30 { width: 30%; }
.w-pct-31 { width: 31%; }
.w-pct-32 { width: 32%; }
.w-pct-33 { width: 33%; }
.w-pct-34 { width: 34%; }
.w-pct-35 { width: 35%; }
.w-pct-36 { width: 36%; }
.w-pct-37 { width: 37%; }
.w-pct-38 { width: 38%; }
.w-pct-39 { width: 39%; }
.w-pct-40 { width: 40%; }
.w-pct-41 { width: 41%; }
.w-pct-42 { width: 42%; }
.w-pct-43 { width: 43%; }
.w-pct-44 { width: 44%; }
.w-pct-45 { width: 45%; }
.w-pct-46 { width: 46%; }
.w-pct-47 { width: 47%; }
.w-pct-48 { width: 48%; }
.w-pct-49 { width: 49%; }
.w-pct-50 { width: 50%; }
.w-pct-51 { width: 51%; }
.w-pct-52 { width: 52%; }
.w-pct-53 { width: 53%; }
.w-pct-54 { width: 54%; }
.w-pct-55 { width: 55%; }
.w-pct-56 { width: 56%; }
.w-pct-57 { width: 57%; }
.w-pct-58 { width: 58%; }
.w-pct-59 { width: 59%; }
.w-pct-60 { width: 60%; }
.w-pct-61 { width: 61%; }
.w-pct-62 { width: 62%; }
.w-pct-63 { width: 63%; }
.w-pct-64 { width: 64%; }
.w-pct-65 { width: 65%; }
.w-pct-66 { width: 66%; }
.w-pct-67 { width: 67%; }
.w-pct-68 { width: 68%; }
.w-pct-69 { width: 69%; }
.w-pct-70 { width: 70%; }
.w-pct-71 { width: 71%; }
.w-pct-72 { width: 72%; }
.w-pct-73 { width: 73%; }
.w-pct-74 { width: 74%; }
.w-pct-75 { width: 75%; }
.w-pct-76 { width: 76%; }
.w-pct-77 { width: 77%; }
.w-pct-78 { width: 78%; }
.w-pct-79 { width: 79%; }
.w-pct-80 { width: 80%; }
.w-pct-81 { width: 81%; }
.w-pct-82 { width: 82%; }
.w-pct-83 { width: 83%; }
.w-pct-84 { width: 84%; }
.w-pct-85 { width: 85%; }
.w-pct-86 { width: 86%; }
.w-pct-87 { width: 87%; }
.w-pct-88 { width: 88%; }
.w-pct-89 { width: 89%; }
.w-pct-90 { width: 90%; }
.w-pct-91 { width: 91%; }
.w-pct-92 { width: 92%; }
.w-pct-93 { width: 93%; }
.w-pct-94 { width: 94%; }
.w-pct-95 { width: 95%; }
.w-pct-96 { width: 96%; }
.w-pct-97 { width: 97%; }
.w-pct-98 { width: 98%; }
.w-pct-99 { width: 99%; }
.w-pct-100 { width: 100%; }

