/* assets/gis-ui.css
 * ---------------------------------------------------------------------------
 * GIS PROCUREMENT-TO-PAYMENT — SHARED DESIGN SYSTEM (29 July 2026)
 *
 * WHY THIS FILE EXISTS
 * --------------------
 * Four pages had four private stylesheets. The result was visible: the primary
 * button was green on the Finance CRM and blue on the Budget Dashboard, the
 * two sign-in cards had different padding, alignment and heading sizes, and the
 * sidebar changed shape between pages. Nobody decided any of that — it is what
 * happens when the same control is defined in four places.
 *
 * One stylesheet means a control cannot look different on two pages, because
 * there is only one definition of it.
 *
 * CONTRAST
 * --------
 * Measured on the live portal before this file existed: six text styles failed
 * WCAG AA, the worst at 2.31:1 against a 4.5:1 requirement (the "locked" stage
 * cards, which are exactly the thing a requester needs to read to understand
 * why they cannot proceed). Every neutral below is chosen to pass on both
 * --surface and --bg:
 *
 *   --text     #101828 on #fff = 16.9:1
 *   --muted    #475467 on #fff =  8.1:1
 *   --subtle   #667085 on #fff =  5.4:1   <- lightest permitted for body text
 *
 * There is deliberately no lighter neutral. If a design needs one, the design
 * is wrong, not the palette.
 * ------------------------------------------------------------------------- */

:root {
  /* Brand. GIS green is the primary action colour everywhere. */
  /* ALIGNED TO overview.html, 30 July 2026.
   * The landing page was rebuilt on its own 45-token scale and the rest of
   * the portal stayed on this one, so two greens, two greys and two page
   * backgrounds shipped side by side. Nobody reports that as a bug; it just
   * reads as slightly cheap. These are now overview's exact values, so a
   * person moving from the overview to a report sees one product. */
  --brand:        #167346;
  --brand-strong: #125A37;
  --brand-soft:   #E8F5EE;
  --brand-ring:   rgba(22,115,70,.32);

  --sidebar:      #0B1120;
  --sidebar-line: #1C2637;
  --sidebar-text: #C7D0DC;   /* 9.6:1 on --sidebar */
  --sidebar-dim:  #8794A8;   /* 4.9:1 on --sidebar — lightest permitted */

  --bg:      #F5F7F9;
  --surface: #FFFFFF;
  --raised:  #FBFCFD;

  --text:   #17202A;   /* 14.8:1 on white */
  --muted:  #53606D;   /*  6.4:1 on white */
  /* MEASURED, NOT INHERITED. overview.html carries this value with a comment
   * claiming 4.6:1. It is 4.32:1 on white and 4.02:1 on the page background —
   * it fails AA, and the comment is what stopped anyone checking. Darkened to
   * the lightest tone in the same family that clears 4.5:1 on the WORST
   * surface it ever sits on, which is the page, not white. */
  /* CORRECTED 29 Aug 2026, and the correction is instructive. The note above
   * has the method exactly right — take the worst surface, not white — but it
   * measured against the page and stopped there. It missed the surface that is
   * darker still: the .step-status pill's own #F3F4F6 background, where
   * #67737F measures 4.40, and the page itself, which is #F5F5F7 and gives
   * 4.45 rather than the 4.51 claimed. Both fail 1.4.3 by a hair, and the
   * text that fails says "Locked until approved" — status about money.
   *
   * #636E7A is the next tone in the same family that clears 4.5:1 on EVERY
   * surface this token lands on, checked rather than assumed:
   *   white #FFFFFF 5.20 · page #F5F5F7 4.77 · pill #F3F4F6 4.72
   *   zebra #F9FAFB 4.97 · card #FAFAFB 4.98
   * The shift is imperceptible next to the old value; the failure it removes
   * was not. Recompute all five if a surface colour ever changes. */
  --subtle: #636E7A;

  --line:        #DFE5EB;
  --line-strong: #C7D0D9;

  /* Overview's vocabulary, so either name works and neither page has to be
   * rewritten to match the other. */
  --text-primary:   var(--text);
  --text-secondary: var(--muted);
  --text-muted:     var(--subtle);
  --surface-page:   var(--bg);
  --surface-card:   var(--surface);
  --border-default: var(--line);
  --border-strong:  var(--line-strong);

  /* Status. Success is a deeper green than the brand so an "approved" badge
   * cannot be mistaken for a button. */
  --ok:       #027A48;  --ok-bg:    #ECFDF3;  --ok-line:    #A6EFC6;
  --warn:     #B54708;  --warn-bg:  #FFFAEB;  --warn-line:  #FEDF89;
  --danger:   #B42318;  --danger-bg:#FEF3F2;  --danger-line:#FECDCA;
  --info:     #175CD3;  --info-bg:  #EFF8FF;  --info-line:  #B2DDFF;

  --r-sm: 6px;  --r:   10px;  --r-lg: 14px;  --r-pill: 999px;
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px; --sp-7: 48px;

  /* CONTENT WIDTH (R5, 30 Aug 2026)
     ------------------------------------------------------------------
     Every page defined its own `.wrap{max-width:…}` and they had drifted:
     1000 on seven pages, 1020 on one, 1100 on two, 1250 on one. The 1020
     is the tell — nobody chooses 1020, it is 1000 that someone nudged.

     Three tiers, because the pages genuinely differ. A requisition form
     and a twelve-column finance grid should not be the same width; the
     defect was that the difference was accidental rather than chosen.

       --w-form   single-column forms, lists, settings
       --w-table  dashboards carrying a wide table
       --w-grid   the densest grids (Finance CRM, Overview)

     WHAT THESE ARE NOT: breakpoints. `@media (max-width:1099px)` in
     index.html pairs with `@media (min-width:1100px)` — the standard
     non-overlapping pair, 1100 minus one pixel, deliberate and correct.
     An earlier count of "six distinct widths" reached that number by
     mixing breakpoints in with content widths. They are different things
     and unifying across the two would break the layout. */
  --w-form:  1000px;
  --w-table: 1100px;
  --w-grid:  1250px;

  --shadow-sm: 0 1px 2px rgba(16,24,40,.05);
  --shadow:    0 1px 3px rgba(16,24,40,.10), 0 1px 2px rgba(16,24,40,.06);
  --shadow-lg: 0 8px 24px rgba(16,24,40,.10);

  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* ---- Reset ------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 var(--font);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ---- App shell ---------------------------------------------------------
 * The old layout let the sidebar stop partway down and the content stop short
 * of the right edge, leaving dead grey on every page at desktop widths. The
 * shell now owns the full viewport and the content column is the only thing
 * that scrolls. */
.gis-app {
  display: grid;
  grid-template-columns: 248px minmax(0, 1fr);
  min-height: 100vh;
}

.gis-side {
  background: var(--sidebar);
  color: var(--sidebar-text);
  padding: var(--sp-4) 0 var(--sp-6);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

.gis-main { min-width: 0; display: flex; flex-direction: column; }

/* ---- Brand block ------------------------------------------------------- */
.gis-brand {
  display: flex; align-items: center; gap: 10px;
  padding: var(--sp-2) var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--sidebar-line);
  margin-bottom: var(--sp-3);
}
.gis-brand__mark {
  width: 34px; height: 34px; flex: 0 0 34px; border-radius: 9px;
  background: var(--brand); color: #fff;
  display: grid; place-items: center; font-weight: 700; font-size: 15px;
}
.gis-brand__name { font-size: 13.5px; font-weight: 650; color: #fff; line-height: 1.25; }
.gis-brand__org  { font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
                   color: var(--sidebar-dim); margin-top: 3px; }

/* ---- Navigation -------------------------------------------------------- */
.gis-navgroup {
  font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--sidebar-dim); padding: var(--sp-4) var(--sp-4) var(--sp-2);
}
.gis-nav { display: flex; align-items: center; gap: 10px;
  padding: 9px var(--sp-3); margin: 1px var(--sp-3); border-radius: var(--r-sm);
  color: var(--sidebar-text); text-decoration: none; font-size: 14px;
  transition: background .12s ease, color .12s ease; }
.gis-nav:hover { background: rgba(255,255,255,.07); color: #fff; }
.gis-nav[aria-current="page"] { background: var(--brand); color: #fff; font-weight: 600; }
.gis-nav__icon { width: 16px; text-align: center; opacity: .85; flex: 0 0 16px; }
.gis-side__foot { margin-top: auto; padding: var(--sp-4);
  font-size: 11.5px; color: var(--sidebar-dim); line-height: 1.5; }

/* ---- Page header ------------------------------------------------------- */
.gis-head {
  background: var(--surface); border-bottom: 1px solid var(--line);
  padding: var(--sp-5) var(--sp-6);
  position: sticky; top: 0; z-index: 20;
}
.gis-head h1 { margin: 0; font-size: 20px; font-weight: 650; letter-spacing: -.01em; }
.gis-head p  { margin: 5px 0 0; color: var(--muted); font-size: 13.5px; }

.gis-body { padding: var(--sp-5) var(--sp-6) var(--sp-7); flex: 1; }

/* Form-heavy pages get a measured column. Centring it — and the header text
 * with it — stops the page reading as "content stuck to the left edge with a
 * void beside it" on a wide monitor. The header and body share one width so
 * the two never drift out of alignment. */
.gis-body--narrow { max-width: 900px; margin-inline: auto; width: 100%; }
.gis-head--narrow > * { max-width: 900px; margin-inline: auto; }

/* ---- Cards ------------------------------------------------------------- */
.gis-app .card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--sp-5);
  margin-bottom: var(--sp-4); box-shadow: var(--shadow-sm);
}
.gis-app .card > h2, .gis-app .card__title {
  margin: 0 0 var(--sp-1); font-size: 16.5px; font-weight: 650; letter-spacing: -.005em;
  display: flex; align-items: center; gap: 2px;
}
.gis-app .card .why, .gis-app .card__sub { margin: 0 0 var(--sp-4); color: var(--muted); font-size: 13.5px; }
.gis-app .card h3 { margin: var(--sp-5) 0 var(--sp-2); font-size: 13.5px; font-weight: 650; }

/* ---- Buttons -----------------------------------------------------------
 * One primary colour. The Budget Dashboard's blue "Open dashboard" and the
 * Finance CRM's green "Open reports" were the same action wearing two colours. */
.gis-app button, .gis-app .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 9px 16px; border: 1px solid transparent; border-radius: var(--r-sm);
  background: var(--brand); color: #fff;
  font: 600 14px/1.2 var(--font); cursor: pointer; text-decoration: none;
  transition: background .12s ease, box-shadow .12s ease, border-color .12s ease;
}
.gis-app button:hover, .gis-app .btn:hover { background: var(--brand-strong); }
.gis-app button.ghost, .gis-app .btn--ghost {
  background: var(--surface); color: var(--text); border-color: var(--line-strong);
}
.gis-app button.ghost:hover, .gis-app .btn--ghost:hover { background: var(--raised); border-color: var(--subtle); }
.gis-app button.danger, .gis-app .btn--danger { background: var(--danger); }
.gis-app button.danger:hover { background: #912018; }
button:disabled, .btn:disabled, button[aria-disabled="true"] {
  opacity: .55; cursor: not-allowed; background: var(--brand);
}
.btn--block { width: 100%; }

/* Focus is visible and consistent. It was previously the browser default,
 * removed in places, which makes keyboard navigation guesswork. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--brand-ring);
  border-radius: var(--r-sm);
}

/* ---- Forms ------------------------------------------------------------- */
.gis-app label { display: block; font-size: 13px; font-weight: 600; margin: var(--sp-3) 0 5px; color: var(--text); }
.gis-app input, .gis-app select, .gis-app textarea {
  width: 100%; padding: 9px 11px; border: 1px solid var(--line-strong);
  border-radius: var(--r-sm); font: 14px/1.4 var(--font); color: var(--text);
  background: var(--surface); transition: border-color .12s ease;
}
.gis-app input::placeholder { color: var(--subtle); }
.gis-app input:hover, .gis-app select:hover { border-color: var(--subtle); }
.gis-app .row { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
.gis-app .row > * { flex: 1 1 200px; }
.gis-app .hint { color: var(--muted); font-size: 12.5px; margin-top: 5px; }

/* ---- Sign-in card ------------------------------------------------------
 * Both gates now use one component, left-aligned. Centred body copy is harder
 * to read and the two pages centred it differently anyway. */
.gis-gate { display: grid; place-items: start center; padding: var(--sp-7) var(--sp-4); }
.gis-gate__card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--shadow); padding: var(--sp-5); width: 100%; max-width: 400px;
}
.gis-gate__card h2 { margin: 0 0 var(--sp-2); font-size: 18px; font-weight: 650; }
.gis-gate__card p  { margin: 0 0 var(--sp-4); color: var(--muted); font-size: 13.5px; }

/* ---- Alerts ------------------------------------------------------------ */
.gis-app .ok, .gis-app .warn, .gis-app .err, .gis-app .info {
  padding: 11px 13px; border-radius: var(--r-sm); margin-top: var(--sp-3);
  font-size: 13.5px; border: 1px solid;
}
.ok   { background: var(--ok-bg);     color: var(--ok);     border-color: var(--ok-line); }
.warn { background: var(--warn-bg);   color: var(--warn);   border-color: var(--warn-line); }
.err  { background: var(--danger-bg); color: var(--danger); border-color: var(--danger-line); }
.info { background: var(--info-bg);   color: var(--info);   border-color: var(--info-line); }
.ok strong, .warn strong, .err strong, .info strong { font-weight: 700; }

/* ---- Tables ------------------------------------------------------------ */
.gis-app table { width: 100%; border-collapse: collapse; margin-top: var(--sp-3); font-size: 13.5px; }
.gis-app th, .gis-app td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--line);
         vertical-align: top; }
.gis-app th { color: var(--muted); font-weight: 600; font-size: 11.5px;
     text-transform: uppercase; letter-spacing: .04em; background: var(--raised); }
.gis-app tbody tr:hover { background: var(--raised); }
.gis-app tbody tr:last-child td { border-bottom: 0; }
.num { text-align: right; font-variant-numeric: tabular-nums; }

/* ---- Pills and badges -------------------------------------------------- */
.gis-app .pill, .gis-app .badge {
  display: inline-block; padding: 3px 9px; border-radius: var(--r-pill);
  font-size: 11.5px; font-weight: 600; background: var(--info-bg); color: var(--info);
  margin: 1px 3px 1px 0; border: 1px solid var(--info-line);
}
.badge--ok     { background: var(--ok-bg);     color: var(--ok);     border-color: var(--ok-line); }
.badge--warn   { background: var(--warn-bg);   color: var(--warn);   border-color: var(--warn-line); }
.badge--danger { background: var(--danger-bg); color: var(--danger); border-color: var(--danger-line); }
.badge--muted  { background: #F2F4F7; color: var(--muted); border-color: var(--line); }

/* ---- Step markers ------------------------------------------------------ */
.gis-app .step {
  display: inline-grid; place-items: center; width: 25px; height: 25px; flex: 0 0 25px;
  border-radius: 50%; background: var(--brand); color: #fff;
  font-size: 12.5px; font-weight: 700; margin-right: var(--sp-2);
}
.step.done { background: var(--ok); }
.step.todo { background: #F2F4F7; color: var(--muted); border: 1px solid var(--line-strong); }

/* ---- Code and credentials ---------------------------------------------- */
.gis-app pre, .gis-app .gis-app .keybox {
  background: #0D1117; color: #C9D1D9; padding: var(--sp-4);
  border-radius: var(--r); overflow: auto; font: 12.5px/1.5 var(--mono);
  margin: var(--sp-3) 0 0;
}
.gis-app pre { max-height: 340px; }
.keybox { color: #7EE787; font-size: 13.5px; word-break: break-all; }

/* ---- Utilities --------------------------------------------------------- */
.gis-app .muted  { color: var(--muted);  font-size: 13px; }
.gis-app .subtle { color: var(--subtle); font-size: 12.5px; }
.stack > * + * { margin-top: var(--sp-3); }
.spread { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }

.skip-link {
  position: absolute; left: -9999px; top: 0; background: var(--brand); color: #fff;
  padding: 10px 16px; z-index: 100; border-radius: 0 0 var(--r-sm) 0;
}
.skip-link:focus { left: 0; }

/* ---- Responsive --------------------------------------------------------
 * The sidebar becomes a horizontal strip rather than disappearing, so the
 * navigation is never unreachable on a tablet or phone. */
@media (max-width: 900px) {
  .gis-app { grid-template-columns: 1fr; }
  .gis-side { position: static; height: auto; flex-direction: row; flex-wrap: wrap;
              align-items: center; padding: var(--sp-3); gap: var(--sp-1); }
  .gis-brand { border-bottom: 0; margin: 0 var(--sp-3) 0 0; padding: 0; }
  .gis-navgroup { display: none; }
  .gis-nav { margin: 0; padding: 7px 11px; font-size: 13px; }
  .gis-side__foot { display: none; }
  .gis-head, .gis-body { padding-left: var(--sp-4); padding-right: var(--sp-4); }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

@media print {
  .gis-side, .gis-head { display: none; }
  .gis-app { display: block; }
  .card { break-inside: avoid; box-shadow: none; }
}

/* ===========================================================================
 * MOBILE NAVIGATION, ENTERPRISE SHELL AND PRINT (29 July 2026)
 *
 * BASELINE DEFECT THIS FIXES
 * --------------------------
 * At 390px the sidebar was `display:none` and nothing replaced it. A phone
 * user could reach the request form and then had no way to get to Finance
 * Reporting, the Budget Dashboard, the overview or administration — the
 * navigation did not degrade, it disappeared. Hiding the only navigation is
 * not a responsive strategy.
 * ======================================================================== */

/* ---- Application bar (mobile only) --------------------------------------
 *
 * THE BAR MUST NOT EAT WHAT YOU SCROLL TO  (29 August 2026)
 * Measured on the live portal at 375px: the bar is sticky, 65px tall and
 * sits at z-index 60, and `html` had scroll-padding-top:auto. Anything
 * scrolled or anchored to therefore landed BEHIND it. Caught on the line
 * "…attached file needs re-attaching, so attach files last" — an
 * instruction about losing an attachment, hidden by furniture.
 *
 * scroll-padding-top belongs on the scroll container, so it is set here
 * beside the bar whose height it compensates for. 72px = 65 + 7 of air.
 * Desktop is unaffected: the bar is display:none there, and the property
 * only has an effect when something scrolls to an anchor.
 * ------------------------------------------------------------------------- */
html { scroll-padding-top: 72px; }

.gis-appbar {
  display: none;
  position: sticky; top: 0; z-index: 60;
  align-items: center; gap: var(--sp-3);
  padding: 10px var(--sp-4);
  background: var(--sidebar); color: #fff;
  border-bottom: 1px solid var(--sidebar-line);
}
.gis-appbar__title { font-size: 15px; font-weight: 650; line-height: 1.2; }
.gis-appbar__sub   { font-size: 11.5px; color: var(--sidebar-dim); }

.gis-menu {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;            /* 44px touch target, not decoration */
  border-radius: var(--r-sm);
  background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.14);
  color: #fff; cursor: pointer; flex: 0 0 44px; padding: 0;
}
.gis-menu:hover { background: rgba(255,255,255,.16); }

.gis-scrim {
  display: none; position: fixed; inset: 0; z-index: 70;
  background: rgba(8,20,38,.55);
}
.gis-scrim[data-open="true"] { display: block; }

/* ---- Drawer ------------------------------------------------------------- */
@media (max-width: 900px) {
  .gis-appbar { display: flex; }
  .gis-app { grid-template-columns: 1fr; }

  /* The sidebar becomes a drawer rather than vanishing. Every destination
   * available on a desktop remains reachable on a phone. */
  .gis-side {
    position: fixed; z-index: 80; top: 0; left: 0;
    width: min(84vw, 300px); height: 100vh;
    flex-direction: column; flex-wrap: nowrap; align-items: stretch;
    padding: var(--sp-4) 0 var(--sp-6);
    transform: translateX(-102%);
    transition: transform .22s ease;
    box-shadow: var(--shadow-lg);
    overflow-y: auto;
  }
  .gis-side[data-open="true"] { transform: translateX(0); }
  .gis-brand { border-bottom: 1px solid var(--sidebar-line);
               margin: 0 0 var(--sp-3); padding: var(--sp-2) var(--sp-4) var(--sp-5); }
  .gis-navgroup { display: block; }
  .gis-nav { margin: 1px var(--sp-3); padding: 11px var(--sp-3); font-size: 15px; }
  .gis-side__foot { display: block; }
  .gis-head, .gis-body { padding-left: var(--sp-4); padding-right: var(--sp-4); }
  body[data-drawer="open"] { overflow: hidden; }   /* no background scroll */
}

@media (min-width: 901px) {
  .gis-side { transform: none !important; }
}

/* ---- Print --------------------------------------------------------------
 * The printed page was a web page with a dark column down the side and cards
 * split across page breaks. This produces a process guide instead. */
@media print {
  .gis-side, .gis-appbar, .gis-scrim, .gis-menu,
  .screen-only, .skip-link, .page-actions { display: none !important; }

  .gis-app { display: block; }
  .gis-main, .gis-body, .gis-body--narrow { max-width: none; margin: 0; padding: 0; }
  .gis-head { position: static; border-bottom: 1px solid #999; padding: 0 0 8pt; }

  body { background: #fff; font-size: 11pt; }
  .card, .ov-card, .ov-route, .ctl-card, details, .ov-diagram, .stepper {
    break-inside: avoid; page-break-inside: avoid;
    box-shadow: none; border: 1px solid #bbb;
  }
  h1, h2, h3 { break-after: avoid; page-break-after: avoid; }
  details[open] > summary ~ * { display: block; }   /* answers must print */
  details > summary { list-style: none; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; color: #555; }
  a[href^="/"]::after, a[href^="?"]::after, a[href^="#"]::after { content: ""; }
}

/* ===========================================================================
   REPORTING TABLES, STATUS AND PRINT                       (30 July 2026)

   Added after a UI review of Finance Reporting and the Budget Dashboard.
   These two pages carry nine tables between them and are the pages people
   print, so they need rules the form pages never did.

   The review also corrected a claim I had made earlier from a bad
   measurement: I reported that both pages lacked focus rings, print styles,
   sticky headers and tabular numerals. They did not. I had scanned only each
   page's inline <style> and missed this stylesheet, which had provided all
   four for some time. Recorded because the same mistake is easy to repeat.
   ======================================================================== */

/* ---- Scanning a long table ---------------------------------------------
   Zebra banding is the cheapest accuracy aid there is: reading a figure off
   the wrong row is the single most common error with dense financial tables,
   and it is silent when it happens. Kept very light so it reads as texture
   rather than decoration. */
.gis-table tbody tr:nth-child(even) { background: #FAFBFC; }
.gis-table tbody tr:hover { background: var(--brand-soft); }
.gis-table th {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface); border-bottom: 2px solid var(--line-strong);
  font-size: 12px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); font-weight: 650;
}
/* Money and quantities line up on the decimal, always. Proportional digits
   in a column of figures make comparison a manual exercise. */
.gis-table td.num, .gis-table th.num,
.kpi__value, .money { font-variant-numeric: tabular-nums; }

/* A wide table on a narrow screen must scroll, not overflow the page and
   take the layout with it. The shadow hints there is more to the right —
   without it people simply do not discover the remaining columns. */
.table-scroll {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  background:
    linear-gradient(90deg, var(--surface) 30%, rgba(255,255,255,0)) left / 32px 100% no-repeat,
    linear-gradient(90deg, rgba(255,255,255,0), var(--surface) 70%) right / 32px 100% no-repeat,
    linear-gradient(90deg, rgba(16,24,40,.10), rgba(16,24,40,0)) left / 10px 100% no-repeat,
    linear-gradient(90deg, rgba(16,24,40,0), rgba(16,24,40,.10)) right / 10px 100% no-repeat;
  background-attachment: local, local, scroll, scroll;
}

/* ---- Status must not depend on colour alone ----------------------------
   Roughly one man in twelve cannot reliably separate the red from the green
   here, and these particular badges say whether a budget line is overspent.
   Each state therefore carries a glyph as well as a hue, so the meaning
   survives colour-blindness, a greyscale print, and a projector. */
.gis-status { display:inline-flex; align-items:center; gap:6px;
  padding:2px 10px; border-radius:var(--r-pill); font-size:12px; font-weight:650;
  border:1px solid transparent; }
.gis-status::before { font-size:11px; line-height:1; }
.gis-status--ok     { color:var(--ok);     background:var(--ok-bg);     border-color:var(--ok-line); }
.gis-status--ok::before     { content:"\2713"; }          /* check */
.gis-status--warn   { color:var(--warn);   background:var(--warn-bg);   border-color:var(--warn-line); }
.gis-status--warn::before   { content:"\25B2"; }          /* up-triangle */
.gis-status--danger { color:var(--danger); background:var(--danger-bg); border-color:var(--danger-line); }
.gis-status--danger::before { content:"\25CF"; }          /* filled circle */
.gis-status--info   { color:var(--info);   background:var(--info-bg);   border-color:var(--info-line); }
.gis-status--info::before   { content:"\2139"; }          /* information */

/* Utilisation bars carry the same three meanings as the badges, and must
   agree with them. A bar that says amber beside a badge that says red is
   worse than no bar at all. */
.gis-bar { height:6px; border-radius:3px; background:var(--line); overflow:hidden; }
.gis-bar > span { display:block; height:100%; background:var(--ok); }
.gis-bar--warn   > span { background:var(--warn); }
.gis-bar--danger > span { background:var(--danger); }

/* ---- Filters people can see they have applied -------------------------- */
.gis-filterbar { display:flex; flex-wrap:wrap; gap:var(--sp-2); align-items:center;
  padding:var(--sp-3) 0; }
.gis-chip { display:inline-flex; align-items:center; gap:6px; min-height:32px;
  padding:4px 10px; border-radius:var(--r-pill); font-size:13px;
  background:var(--brand-soft); color:var(--brand-strong);
  border:1px solid var(--brand); }
.gis-chip button { border:0; background:none; cursor:pointer; font-size:14px;
  line-height:1; color:inherit; padding:2px 4px; min-width:24px; }

/* ---- Honest empty states -----------------------------------------------
   "No data" and "no data MATCHING YOUR FILTER" and "you may not see this
   data" are three different sentences with three different remedies, and a
   blank panel says none of them. */
.gis-empty { text-align:left; max-width:620px; margin:32px auto;
  padding:20px 22px; border-radius:var(--r-lg);
  background:var(--raised); border:1px solid var(--line); }
.gis-empty h3 { margin:0 0 6px; font-size:16px; color:var(--text); }
.gis-empty p  { margin:0; color:var(--muted); font-size:14px; line-height:1.6; }
.gis-empty--refused { background:var(--warn-bg); border-color:var(--warn-line); }

/* ---- Printing a report -------------------------------------------------
   Finance print these. A screen layout on paper wastes a page on navigation
   and then splits a table across a page break mid-row. */
@media print {
  .gis-sidebar, .gis-nav, .tabs, .gis-filterbar, .page-actions,
  #lockBtn, #reloadBtn { display:none !important; }
  .gis-table { page-break-inside:auto; }
  .gis-table tr { page-break-inside:avoid; page-break-after:auto; }
  .gis-table thead { display:table-header-group; }   /* repeat on every page */
  .gis-table tbody tr:nth-child(even) { background:transparent; }
  .gis-table th { border-bottom:1px solid #000; color:#000; }
  .table-scroll { overflow:visible !important; background:none !important; }
  .gis-status { border:1px solid #666 !important; background:none !important;
                color:#000 !important; }
  .card, .panel { box-shadow:none !important; border:1px solid #999; }
  a[href]::after { content:""; }        /* URLs after every link are noise */
}

/* The [hidden] attribute is only user-agent display:none, and any author
   rule like `.nav{display:flex}` silently defeats it — measured live on the
   deployed overview: the attribute was set and the admin link was still in
   the layout. The default must not be losable to a specificity accident. */
[data-admin-only][hidden] { display: none !important; }

/* ---------------------------------------------------------------------------
   PRODUCTION CREDIT (2 Aug 2026)

   One quiet line at the foot of every page: the school's copyright first,
   because GIS owns the system and the data in it, then the builder. That
   order is the enterprise convention and it is also the accurate one.

   Deliberately understated — 11.5px, muted, centred, no logo. A vendor mark
   that competes with the client's own brand at the top of the page reads as
   advertising; one that sits quietly at the bottom reads as provenance.

   The outbound link carries rel="noopener noreferrer": it opens a third-party
   site from a page that handles procurement data, so the new tab must get no
   handle back to this window and no referrer.

   NOTE ON CSP: a plain navigation link needs no Content-Security-Policy
   change. `form-action` governs form POSTs, not anchors, so _headers is
   untouched by this.
--------------------------------------------------------------------------- */
.gis-credit {
  margin: 0;
  padding: 14px 0 2px;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--muted, #667085);
  text-align: center;
}
.gis-credit a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid rgba(102, 112, 133, .35);
}
.gis-credit a:hover,
.gis-credit a:focus-visible { color: var(--green, #1F4E4B); border-bottom-color: currentColor; }

/* ---------------------------------------------------------------------------
   SIDEBAR SECTIONS — collapse, and the show-all escape hatch  (10 Aug 2026)
   Styles live here, once, because the sidebar markup is copied into ten pages
   and gis-nav.js enhances it in place. A page-local style would apply to one.
   --------------------------------------------------------------------------- */
/* TARGET SIZE (29 Aug 2026). Measured at 232 x 16px on the live portal —
   below the 24x24 minimum in WCAG 2.2 AA (2.5.8 Target Size). The row is
   already full width, so only the height was at fault. 32px is comfortable
   and costs no vertical rhythm: the 8px bottom margin absorbed the change,
   and the label stays optically where it was because the box centres it. */
.side-sec-toggle{
  display:flex; align-items:center; justify-content:space-between; gap:8px;
  width:100%; background:none; border:0; cursor:pointer;
  min-height:32px;
  padding:0 12px; margin-bottom:8px;
  color:rgba(255,255,255,0.45); text-align:left; font:inherit;
  letter-spacing:.14em; text-transform:uppercase; font-size:10px; font-weight:800;
}
.side-sec-toggle:hover{ color:rgba(255,255,255,0.75); }
.side-sec-toggle:focus-visible{ outline:2px solid #7FB8B2; outline-offset:2px; border-radius:6px; }
.side-chev{ font-size:13px; line-height:1; opacity:.7; font-weight:700; }
.side-sec-body[hidden]{ display:none; }
.side-showall{
  display:block; width:calc(100% - 24px); margin:6px 12px 10px;
  padding:7px 10px; border-radius:8px; cursor:pointer;
  background:rgba(255,255,255,0.06); border:1px dashed rgba(255,255,255,0.22);
  color:rgba(255,255,255,0.55); font-size:11px; font-weight:600; text-align:left;
}
.side-showall:hover{ color:#fff; background:rgba(255,255,255,0.1); }
