/* PSG Ops — dressed in Parrish Security Group's own identity.
   ---------------------------------------------------------------------------
   Palette, type and logo are lifted from parrishsecuritygroup.com so the app
   reads as PSG's, not as a generic internal tool:

     #28276B  deep indigo   — their primary. Chrome, headings, the duty strip.
     #269CD2  sky blue      — their secondary. Accents and active states.
     #333333 / #747474      — their body and muted greys.
     Roboto + Saira         — their body and display faces.

   Brand colours carry identity; they do NOT carry status. Approved / expired /
   needs-attention keep their own semantic colours, because a guard must be able
   to read state at a glance without knowing the brand.

   Two people use this: a guard on post — gloved, on a phone, in sunlight or at
   0300 — and a manager scanning for what needs a decision. Everything serves
   one of them. */

:root {
  /* PSG brand */
  --brand: #28276b;         /* deep indigo */
  --brand-deep: #1d1c50;    /* pressed / darker indigo */
  --brand-tint: #eeeef5;    /* indigo at 6% — quiet fills */
  --sky: #269cd2;           /* bright accent for borders and rules */
  --sky-deep: #1a7ba8;
  --accent: #2f9bd4;        /* interaction — active states, focus */
  --accent-ink: #1877a3;    /* the text-safe sky, 4.7:1 on white */

  /* Command chrome — the deep-indigo operations bar. The work canvas below
     stays light for sunlight; only the chrome goes dark, which is what turns
     the app from "website" into "console". */
  --command-a: #201f4d;
  --command-b: #2c2b63;
  --on-command: #d7d8ec;
  --on-command-strong: #ffffff;

  /* Surface — neutrals biased cool/indigo, so they read chosen, not default */
  --paper: #eef0f6;
  --card: #ffffff;
  --card-2: #f7f8fc;
  --line: #e0e2ee;
  --line-soft: #eceef6;
  --ink: #20213a;
  --muted: #6a6c86;
  --faint: #9a9cb4;

  /* Status — deliberately not brand colours, so state reads at a glance */
  --success: #157f5c;
  --danger: #b23a2c;
  --warn: #a9781f;
  --warn-soft: #f7efdb;

  /* Severity ramp (alarms) */
  --sev-low: #2f80c2;
  --sev-med: #c9892b;
  --sev-high: #c0392b;
  --sev-crit: #8f2417;

  /* Type */
  --font-ui: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  /* Saira is loaded as PSG's display face; Bahnschrift/DIN are the condensed,
     technical fallbacks so headings keep their character before it arrives. */
  --font-display: "Saira", "Bahnschrift", "DIN Condensed", "Roboto", "Segoe UI", sans-serif;
  /* Clock times, site codes and badge numbers. A monospaced face keeps a column
     of times true and 1615 unmistakable — and it is the app's operations voice. */
  --font-clock: ui-monospace, "Cascadia Mono", "SF Mono", Consolas, monospace;

  --radius: 12px;
  --radius-sm: 8px;
  --shadow: 0 1px 2px rgba(30, 30, 70, 0.06), 0 6px 20px -8px rgba(30, 30, 70, 0.14);
  --shadow-lift: 0 8px 26px -10px rgba(30, 30, 70, 0.26);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font: 16px/1.55 var(--font-ui);
  background: var(--paper);
  color: var(--ink);
  /* Times, counts and badge numbers line up wherever they appear. */
  font-variant-numeric: tabular-nums;
}

/* ---- Command bar --------------------------------------------------------
   A deep-indigo operations chrome, not a website header. The work canvas below
   stays light for a phone held up in direct sun; only the chrome goes dark. */
.topbar {
  display: flex; align-items: center; gap: 1.4rem; flex-wrap: wrap;
  background: linear-gradient(180deg, var(--command-b), var(--command-a));
  padding: 0.5rem 1.2rem;
  border-bottom: 2px solid var(--accent);
  box-shadow: 0 6px 24px -12px rgba(15, 15, 45, 0.55);
}
/* PSG's own emblem, kept — set on a light plaque so it still reads on the dark
   bar and looks like the badge it is. The wordmark supplies "Ops". */
.brand { display: flex; align-items: center; gap: 0.6rem; text-decoration: none; }
.brand img {
  height: 34px; width: auto; display: block;
  background: #fff; border-radius: 8px; padding: 4px 7px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.28);
}
.brand-word {
  font-family: var(--font-display); font-size: 1.3rem; font-weight: 700;
  color: var(--on-command-strong); letter-spacing: 0.01em; line-height: 1;
}
.topbar nav { display: flex; align-items: center; gap: 1.05rem; flex: 1 1 auto; }
.topbar a {
  color: var(--on-command); text-decoration: none; padding: 0.42rem 0;
  border-bottom: 2px solid transparent; font-size: 0.9rem; font-weight: 500;
}
.topbar a:hover { color: #fff; border-bottom-color: var(--accent); }

/* Thin divider between nav groups — turns one row of a dozen links into three
   legible clusters without hiding anything. */
.nav-sep { flex: 0 0 auto; width: 1px; height: 1.15rem; background: rgba(255, 255, 255, 0.16); }

/* The alert bells, pulled to the right so "places" and "tasks" don't blur
   together. margin-left:auto does the pushing; nav is flex:1 so there's room. */
.nav-alerts { display: inline-flex; align-items: center; gap: 1.05rem; margin-left: auto; }

/* The unread badge. Red because it is the one thing in the bar that means
   "someone is waiting on you" — everything else is a place, this is a task. */
.topbar a.bell { display: inline-flex; align-items: center; gap: 0.4rem; }
.topbar a.bell:hover { color: #fff; }
.topbar a.bell.has-unread { font-weight: 700; color: #fff; }
.badge {
  background: var(--danger); color: #fff; border-radius: 99px;
  font-size: 0.72rem; font-weight: 700; line-height: 1;
  padding: 0.22rem 0.42rem; min-width: 1.1rem; text-align: center;
}
.userbox {
  display: flex; align-items: center; gap: 0.7rem;
  font-size: 0.82rem; color: var(--on-command);
}
.userbox a { color: var(--on-command); }
.userbox a:hover { color: #fff; }
.userbox button.ghost {
  border-color: rgba(255, 255, 255, 0.32); color: var(--on-command); background: transparent;
}
.userbox button.ghost:hover { border-color: #fff; color: #fff; }

/* ---- Duty strip ---------------------------------------------------------
   Shown only while a log is open. A guard leaves their log constantly — to
   check a credential, to file a request — and should never hunt for it. */
.duty {
  background: var(--brand); color: #dcdcea;
  padding: 0.5rem 1.2rem; font-size: 0.85rem;
  display: flex; align-items: center; gap: 0.65rem; flex-wrap: wrap;
}
.duty .on {
  font-family: var(--font-display);
  color: #fff; background: var(--sky-deep);
  border-radius: 3px; padding: 0.1rem 0.45rem;
  font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.09em; font-size: 0.68rem;
}
.duty a { color: #fff; text-decoration: underline; text-underline-offset: 3px; }

/* ---- Layout ------------------------------------------------------------- */
main { max-width: 940px; margin: 1.6rem auto 4rem; padding: 0 1rem; }

h1 {
  font-family: var(--font-display);
  font-size: 1.6rem; margin: 0.3rem 0 0.9rem;
  font-weight: 700; letter-spacing: -0.01em; color: var(--brand);
}
/* Section headings read as stencilled labels rather than prose — the voice of
   a duty board. Saira is semi-condensed, which is what makes this work.

   The trailing rule is what separates one section from the next. Without it
   the dashboard is six blocks of text floating in whitespace, and the eye has
   nothing to tell it where "compliance expiring" stops and the next thing
   starts. The rule runs from the label to the edge, so the heading reads as a
   tab on a divider rather than a line with words above it. */
h2 {
  font-family: var(--font-display);
  font-size: 0.8rem; margin: 2rem 0 0.7rem;
  text-transform: uppercase; letter-spacing: 0.13em; font-weight: 700;
  color: var(--muted);
  display: flex; align-items: center; gap: 0.6rem;
}
h2::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}
/* Inside a card the card's own border already does the dividing, so the rule
   would just be noise on top of it. */
.card h2::after, .notice h2::after { display: none; }
h3 { font-family: var(--font-display); font-size: 1.02rem; margin: 0 0 0.5rem; font-weight: 600; }

.muted { color: var(--muted); font-size: 0.92em; }
.mono { font-family: var(--font-clock); font-variant-numeric: tabular-nums; }
.error { color: var(--danger); font-weight: 600; }

.pagehead { display: flex; justify-content: space-between; align-items: end; gap: 1rem; flex-wrap: wrap; }
.search { display: flex; gap: 0.4rem; }
.search input { min-width: 240px; }

/* ---- Cards -------------------------------------------------------------- */
.card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.1rem 1.2rem; margin: 0.8rem 0; box-shadow: var(--shadow);
}
.card.narrow { max-width: 400px; margin: 3rem auto; }
.card.row { display: flex; justify-content: space-between; gap: 1rem; align-items: center; flex-wrap: wrap; }

/* ---- Dashboard tiles ----------------------------------------------------
   The manager's whole morning as six numbers. Each tile is the tail of a plain
   sentence — "4 waiting on your signature" — and the whole tile links to that
   list, so the dashboard summarises rather than restating every row. */
.statgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.9rem; margin: 1.1rem 0; }
.stat {
  position: relative; display: flex; flex-direction: column; gap: 0.2rem;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.1rem 1.2rem 1.05rem; box-shadow: var(--shadow);
  text-decoration: none; color: var(--ink);
  transition: border-color 0.12s ease, box-shadow 0.12s ease, transform 0.12s ease;
}
.stat:hover { border-color: var(--accent); box-shadow: var(--shadow-lift); transform: translateY(-1px); }
.stat::after {
  content: "→"; position: absolute; top: 0.95rem; right: 1.1rem;
  color: var(--faint); font-size: 1rem; transition: color 0.12s ease, transform 0.12s ease;
}
.stat:hover::after { color: var(--accent-ink); transform: translateX(2px); }
.stat-num {
  font-family: var(--font-display); font-weight: 700; font-size: 2.7rem; line-height: 1;
  letter-spacing: 0.01em; color: var(--brand);
}
.stat-lab { color: var(--muted); font-size: 0.92rem; max-width: 22ch; }
.stat.warn .stat-num { color: var(--warn); }
.stat.crit .stat-num { color: var(--danger); }
.stat.act  .stat-num { color: var(--brand); }
/* Nothing to do: greyed and calm, so the eye skips it. Still a link. */
.stat.is-clear .stat-num { color: var(--faint); }
.stat.is-clear::after { display: none; }
@media (max-width: 780px) { .statgrid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 460px) { .statgrid { grid-template-columns: 1fr; } }

/* ---- Forms -------------------------------------------------------------- */
label { display: block; margin: 0.7rem 0; font-weight: 500; font-size: 0.9rem; }
input, select, textarea {
  display: block; width: 100%; margin-top: 0.3rem; padding: 0.6rem 0.7rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font: inherit; background: #fff; color: var(--ink);
}
textarea { resize: vertical; min-height: 5.5rem; line-height: 1.5; }
input:focus-visible, select:focus-visible, textarea:focus-visible,
button:focus-visible, a:focus-visible {
  outline: 3px solid var(--sky); outline-offset: 2px;
}
label .muted { display: block; font-weight: 400; margin-top: 0.25rem; }

.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0 1rem; align-items: end; }
.grid .wide { grid-column: 1 / -1; }
.grid button { margin: 0.7rem 0; }

button {
  font: inherit; font-family: var(--font-display);
  font-weight: 600; cursor: pointer; letter-spacing: 0.01em;
  background: var(--brand); color: #fff; border: 1px solid var(--brand);
  border-radius: var(--radius-sm); padding: 0.55rem 1.1rem;
  transition: filter 0.12s ease, transform 0.06s ease;
}
button:hover { filter: brightness(1.15); }
button:active { transform: translateY(1px); }
button.ghost { background: #fff; color: var(--brand); border-color: var(--line); }
button.danger { color: var(--danger); border-color: var(--danger); background: #fff; }

/* ---- Tables ------------------------------------------------------------- */
table {
  width: 100%; border-collapse: collapse; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
}
th, td { text-align: left; padding: 0.65rem 0.85rem; border-bottom: 1px solid var(--line); vertical-align: top; }
th {
  background: var(--brand-tint); font-family: var(--font-display);
  font-size: 0.72rem; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--brand); font-weight: 700;
}
tr:last-child td { border-bottom: none; }
td.topic { font-weight: 500; }

/* ---- Status pills ------------------------------------------------------- */
.pill {
  display: inline-block; font-family: var(--font-display);
  padding: 0.2rem 0.6rem; border-radius: 99px; font-size: 0.72rem;
  font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  white-space: nowrap;
}
.pill.pending, .pill.submitted, .pill.expiring { background: var(--warn-soft); color: #7a5c14; }
.pill.approved, .pill.ok                       { background: #dcece5; color: #0f5f44; }
.pill.denied, .pill.expired                    { background: #f4ded9; color: var(--danger); }
.pill.recorded                                 { background: var(--brand-tint); color: var(--brand); }
.pill.draft, .pill.none                        { background: #eaeaea; color: var(--muted); }

.decide { display: flex; gap: 0.5rem; align-items: center; }
.decide input { width: 190px; margin: 0; }

/* ---- Dashboard sections ------------------------------------------------- */
.section { margin: 1.9rem 0; }
.count {
  font-family: var(--font-display);
  font-size: 0.76rem; font-weight: 700; color: var(--brand);
  background: var(--brand-tint); border-radius: 99px; padding: 0.12rem 0.5rem;
  letter-spacing: 0;
}
.count.alarm { color: #fff; background: var(--danger); }
/* "Nothing to do here" reads as reassurance, not absence. Named allclear
   because .clear collides with the .tap.clear button. */
.allclear { color: var(--success); font-weight: 600; font-size: 0.92em; }

.stack { display: flex; flex-direction: column; gap: 0.5rem; }
.rowline { display: flex; justify-content: space-between; gap: 1rem; align-items: center; flex-wrap: wrap; }
.rowline .who { font-weight: 500; }

.notice {
  border-left: 4px solid var(--sky); background: #eef7fc;
  padding: 0.85rem 1rem; border-radius: 0 var(--radius-sm) var(--radius-sm) 0; margin: 1rem 0;
}
.notice.bad { border-left-color: var(--danger); background: #fbf0ee; }

/* ---- Shape B: built for gloved hands on a phone in sunlight -------------
   Every tap variant states its own colour pair rather than inheriting. A
   button nobody can read outdoors is a broken button, not an ugly one. */
.tapgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.6rem; margin: 0.6rem 0; }
.tap {
  min-height: 64px; font-size: 1.05rem; font-weight: 700;
  border-radius: 12px; padding: 0.9rem 1rem; width: 100%;
}
/* Arrive is the action, so it takes the brand blue. Clear is completion, so it
   takes the success green — the same green the station node turns. */
.tap.go    { background: var(--sky-deep); border-color: var(--sky-deep); color: #fff; }
.tap.clear { background: var(--success); border-color: var(--success); color: #fff; }
.tap.ghost { background: #fff; color: var(--brand); border: 2px solid var(--line); }
.tap.warn  { background: var(--danger); border-color: var(--danger); color: #fff; }
.tap:active { transform: translateY(2px); }

/* ---- The patrol route ---------------------------------------------------
   The signature of this app. A patrol is a route walked in order, so it is
   drawn as one: a line down the plant with a station per checkpoint. State is
   the point — a guard glances and sees how far round they are and what is
   still outstanding, which a flat list of identical cards cannot show. */
.route { position: relative; margin: 0.5rem 0 1.5rem; padding-left: 1.9rem; }
.route::before {
  content: ""; position: absolute; left: 0.62rem; top: 0.9rem; bottom: 0.9rem;
  width: 2px; background: var(--line); border-radius: 2px;
}
.station { position: relative; margin: 0 0 0.7rem; }
.station-node {
  position: absolute; left: -1.9rem; top: 0.85rem;
  width: 1.35rem; height: 1.35rem; border-radius: 50%;
  background: var(--paper); border: 2px solid var(--line);
  display: flex; align-items: center; justify-content: center;
  font-size: 0.7rem; font-weight: 700; color: #fff; line-height: 1;
}
.station.arrived .station-node { background: var(--warn); border-color: var(--warn); }
.station.cleared .station-node { background: var(--success); border-color: var(--success); }
.station.cleared .station-node::after { content: "\2713"; }

.checkpoint {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 0.75rem 0.9rem; box-shadow: var(--shadow);
}
.station.arrived .checkpoint { border-color: var(--warn); box-shadow: var(--shadow-lift); }
.station.cleared .checkpoint { background: #fbfdfc; }
.checkpoint h3 { margin: 0 0 0.5rem; font-size: 1rem; display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
.checkpoint .area { font-weight: 400; color: var(--muted); font-size: 0.8rem; }
.checkpoint .state {
  font-family: var(--font-display); font-size: 0.68rem; text-transform: uppercase;
  letter-spacing: 0.1em; font-weight: 700; color: var(--muted);
}
.station.arrived .state { color: #8a660f; }
.station.cleared .state { color: var(--success); }
.checkpoint form { display: inline; }
/* Sized for a gloved thumb, which is the phone case. On a desktop the same
   buttons would run half the width of the screen for no reason, so cap them
   there — the mobile rule below puts them back to full width. */
.cprow { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; max-width: 26rem; }

/* A leg of the patrol — North Plant, South Plant, POTBA. */
.leg {
  display: flex; align-items: center; gap: 0.6rem;
  margin: 1.6rem 0 0.6rem; font-family: var(--font-display);
  font-size: 0.76rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.13em; color: var(--brand);
}
.leg::after { content: ""; flex: 1; height: 2px; background: var(--sky); opacity: 0.35; }

/* ---- The form catalog ---------------------------------------------------
   These were table rows, which made nineteen separate forms read as one
   document — and because each form's note wraps to a different length, the
   rows never lined up. Each form is a thing you start, so each is its own
   card with a real gap between it and the next. */
.formlist { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 0.7rem; }

.formcard {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.2rem; margin: 0;
}
.formcard-body { min-width: 0; }        /* lets long notes wrap instead of pushing the button */
.formcard-action { flex-shrink: 0; }

.formcard-name {
  margin: 0 0 0.3rem; font-size: 1.05rem;
  display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
}
.formcard-note { margin: 0 0 0.5rem; color: var(--muted); font-size: 0.9rem; }

/* The chain is metadata, so it sits quieter than the name. Laid out as a label
   column plus a content column: a long chain wraps within its own column
   instead of running back under the label and stranding a word or two there. */
.formcard-flow {
  margin: 0; font-size: 0.85rem; color: var(--ink);
  display: grid; grid-template-columns: auto 1fr; gap: 0.1rem 0.55rem;
  align-items: baseline;
}
.flow-label {
  font-family: var(--font-display); font-size: 0.65rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted);
}
.flow-steps { grid-column: 2; }
.flow-then { grid-column: 2; color: var(--muted); }

/* Catalogued but not built: present so nothing goes missing, but visibly not
   something you can act on yet. */
.formcard.is-paper { background: #fafafa; border-style: dashed; }
.formcard.is-paper .formcard-name { color: var(--muted); }

/* ---- Compliance ---------------------------------------------------------
   Grouped by credential type per the spec, but sharing one header rather than
   repeating five columns of chrome above every one- or two-row group.

   The real job of this page is ranking: an expired TWIC and one with 65 days
   left were rendering at the same visual weight, so nothing said what to do
   first. A colour bar down the left of each row does that ranking, and it
   works before you have read a single word. */
.tally { display: flex; gap: 0.6rem; margin: 0.9rem 0 1.2rem; flex-wrap: wrap; }
.tally-item {
  font-size: 0.85rem; padding: 0.3rem 0.75rem; border-radius: 99px;
  background: #eaeaea; color: var(--muted);
}
.tally-item strong { font-family: var(--font-display); font-size: 1rem; }
.tally-item.bad { background: #f4ded9; color: var(--danger); }
.tally-item.warn { background: var(--warn-soft); color: #7a5c14; }

.credtable {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow);
}
.credhead, .credrow {
  display: grid;
  grid-template-columns: 1.5fr 0.7fr 1.3fr 1fr 0.8fr;
  gap: 0.9rem; align-items: baseline; padding: 0.6rem 0.9rem;
}
.credhead {
  background: var(--brand-tint); font-family: var(--font-display);
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--brand); font-weight: 700;
}
.credgroup {
  background: #f7f8fa; border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line-soft);
  padding: 0.45rem 0.9rem; font-family: var(--font-display);
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.1em;
  font-weight: 700; color: var(--ink);
  display: flex; align-items: center; gap: 0.5rem;
}
.credrow { border-bottom: 1px solid var(--line); border-left: 4px solid transparent; }
.credrow:last-child { border-bottom: none; }
.cred-who { font-weight: 500; }
.cred-approx { display: block; font-style: normal; font-size: 0.8rem; color: var(--muted); }

/* The ranking. Expired shouts, a fortnight out speaks up, the rest is calm. */
.credrow.is-expired { border-left-color: var(--danger); background: #fdf6f5; }
.credrow.is-urgent  { border-left-color: var(--danger); }
.credrow.is-soon    { border-left-color: var(--warn); }

/* ---- The verification queue ---------------------------------------------
   Each record was a tall card with its facts run together in one sentence, so
   a queue of thirty was a long scroll and the one thing that matters — that a
   date is a GUESS — was buried mid-line. Facts now sit in a label/value list
   where they can be compared against the card in someone's hand, and a record
   on an approximate date is marked before you read anything. */
.verifycard { padding: 0.95rem 1.1rem; }
.verifycard.is-approx { border-left: 4px solid var(--warn); }

.verify-top {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 1rem; flex-wrap: wrap; margin-bottom: 0.6rem;
}
.verify-who h3 { margin: 0; font-size: 1.05rem; }
.verify-type { font-size: 0.85rem; color: var(--muted); }
.verify-flags { display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; }

/* Label/value pairs rather than a sentence: this is a page of comparisons,
   and the eye needs to land on the dates, not read prose to find them. */
.verify-claim {
  display: grid; grid-template-columns: auto 1fr; gap: 0.15rem 0.8rem;
  margin: 0 0 0.8rem; padding: 0.6rem 0.75rem;
  background: #fafbfc; border-radius: var(--radius-sm);
  font-size: 0.9rem;
}
.verify-claim dt {
  font-family: var(--font-display); font-size: 0.65rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.09em; color: var(--muted);
  padding-top: 0.15rem;
}
.verify-claim dd { margin: 0; }
.verify-approx {
  display: inline-block; margin-left: 0.4rem; font-size: 0.78rem;
  color: #8a660f; background: var(--warn-soft);
  padding: 0.05rem 0.45rem; border-radius: 99px;
}

.verify-action {
  display: flex; gap: 0.7rem; align-items: end; flex-wrap: wrap;
}
.verify-action label { margin: 0; font-size: 0.82rem; }
.verify-action input { width: 11rem; }
.verify-hint { margin: 0.45rem 0 0; font-size: 0.82rem; }

/* ---- The approvals inbox ------------------------------------------------
   Every item was three lines of near-identical text with a form bolted on, so
   nothing said where to look first. Now: what kind of form, then who it is
   for, then what they are asking, then which signature is being given — in
   that order, because that is the order the question gets answered in. */
.inbox { padding: 0.95rem 1.1rem; }
.inbox-head {
  display: flex; justify-content: space-between; align-items: center;
  gap: 0.8rem; flex-wrap: wrap; margin-bottom: 0.5rem;
}
.inbox-form {
  font-family: var(--font-display); font-size: 0.7rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.09em;
  color: var(--brand); background: var(--brand-tint);
  padding: 0.2rem 0.55rem; border-radius: 4px;
}
/* Age is the queue's ordering, so it is on every row rather than implied by
   position. Past a week it stops being neutral. */
.inbox-waited { font-size: 0.8rem; color: var(--muted); }
.inbox-waited.is-stale { color: var(--danger); font-weight: 600; }

.inbox-who { margin: 0 0 0.15rem; font-size: 1.05rem; }
.inbox-summary { margin: 0 0 0.35rem; font-size: 0.95rem; }
.inbox-step { margin: 0 0 0.75rem; font-size: 0.85rem; color: var(--muted); }
.inbox-step strong { color: var(--ink); }

.inbox-actions {
  display: flex; align-items: center; gap: 0.9rem; flex-wrap: wrap;
  border-top: 1px solid var(--line-soft); padding-top: 0.75rem;
}
.inbox-decide { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.inbox-decide input { width: 13rem; margin: 0; }
.inbox-open { margin-left: auto; font-size: 0.85rem; white-space: nowrap; }

/* ---- My requests --------------------------------------------------------
   An employee wants one thing from this page: whose desk is it on. A status
   of "submitted" does not answer that; the step label does. */
.mysub {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 0.8rem 1rem; margin-bottom: 0.6rem;
  text-decoration: none; color: inherit;
}
.mysub:hover { border-color: var(--sky); }
.mysub-main { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.mysub-form { font-weight: 600; }
.mysub-summary { font-size: 0.9rem; }
.mysub-main .muted { font-size: 0.8rem; }
.mysub-state { display: flex; flex-direction: column; align-items: flex-end; gap: 0.25rem; flex-shrink: 0; }
.mysub-progress { font-size: 0.75rem; color: var(--muted); }

/* ---- Officer logs -------------------------------------------------------
   A manager's read-back view. One block per officer, then a line per log —
   date, form, site, size, state — so a week of somebody's paperwork can be
   taken in without opening anything. */
.logfilter { display: flex; gap: 1rem; align-items: end; flex-wrap: wrap; margin: 0.9rem 0 0.2rem; }
.logfilter label { margin: 0; font-size: 0.82rem; }
.logfilter select { width: auto; min-width: 9rem; }

.officer { margin: 1.6rem 0; }
.officer-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.8rem; flex-wrap: wrap;
  border-bottom: 2px solid var(--brand); padding-bottom: 0.35rem; margin-bottom: 0.5rem;
}
.officer-name {
  /* Overrides the tracked-uppercase h2: a person's name is a name. */
  font-family: var(--font-display); font-size: 1.15rem; font-weight: 700;
  text-transform: none; letter-spacing: -0.01em; color: var(--brand);
  margin: 0; display: block;
}
.officer-name::after { display: none; }
.officer-stats { font-size: 0.82rem; color: var(--muted); }
.officer-open { color: var(--warn); font-weight: 600; margin-left: 0.4rem; }

.officer-logs { display: flex; flex-direction: column; gap: 2px; }
.loglink {
  display: grid;
  grid-template-columns: 7.5rem 1fr 9rem 6rem 5rem;
  gap: 0.8rem; align-items: center;
  padding: 0.55rem 0.75rem; text-decoration: none; color: inherit;
  background: var(--card); border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
}
.loglink:hover { border-color: var(--sky); background: #fbfdff; }
.loglink.is-open { border-left: 3px solid var(--warn); }
.loglink-date { font-weight: 500; font-size: 0.88rem; }
.loglink-form { font-weight: 500; }
.loglink-site, .loglink-entries { font-size: 0.85rem; color: var(--muted); }
.loglink-state { text-align: right; }

/* ---- Notifications ------------------------------------------------------
   An unread row is a button, not a link, because opening it also marks it read
   — that is a state change, so it posts. A read row is a plain link, since
   re-reading changes nothing. The shapes match on purpose: the difference a
   reader should notice is unread vs read, not button vs link. */
.notice-row {
  display: block; margin: 0 0 0.5rem;
  background: var(--card); border: 1px solid var(--line-soft);
  border-left: 3px solid transparent; border-radius: var(--radius-sm);
  padding: 0.7rem 0.85rem; text-decoration: none; color: inherit;
}
.notice-row:hover { border-color: var(--sky); background: #fbfdff; }
.notice-row.is-unread { border-left-color: var(--danger); }
.notice-row .notice-msg { display: block; font-weight: 500; }
.notice-row .muted { font-size: 0.82rem; }

/* The unread row's button fills its row and drops every button affordance, so
   the whole strip is the tap target — convention 11, even away from a post. */
.notice-open {
  display: block; width: 100%; text-align: left;
  background: none; border: none; padding: 0; margin: 0;
  font: inherit; color: inherit; cursor: pointer;
}
.readall { margin-bottom: 0.75rem; }

@media (max-width: 760px) {
  .loglink {
    grid-template-columns: 1fr auto;
    gap: 0.1rem 0.6rem;
  }
  .loglink-date { grid-column: 1; font-size: 0.8rem; color: var(--muted); }
  .loglink-state { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
  .loglink-form { grid-column: 1; }
  .loglink-site, .loglink-entries { grid-column: 1; }
  .loglink-entries::before { content: "· "; }
  .loglink-site, .loglink-entries { display: inline; }
}

/* ---- Log entries -------------------------------------------------------- */
.entry { display: flex; gap: 0.9rem; padding: 0.6rem 0; border-bottom: 1px solid var(--line-soft); }
.entry:last-child { border-bottom: none; }
.entry time {
  font-family: var(--font-clock); font-size: 0.86rem; font-weight: 600;
  color: var(--brand); white-space: nowrap; min-width: 3.4rem; padding-top: 0.1rem;
}

footer { text-align: center; color: var(--muted); font-size: 0.78rem; padding: 2.5rem 0 1rem; }

/* ---- Sign in ------------------------------------------------------------
   The only page an outsider sees, and a new guard's first impression. The
   emblem was drawn for a light background, so it gets one. */
.signin { min-height: 76vh; display: flex; align-items: center; justify-content: center; padding: 1rem; }
.signin-card {
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  box-shadow: var(--shadow-lift); padding: 2rem 1.8rem; width: 100%; max-width: 380px;
  border-top: 5px solid var(--brand);
}
.signin-card img.mark { height: 74px; width: auto; display: block; margin: 0 auto 1.1rem; }
.signin-card h1 { margin: 0 0 0.25rem; font-size: 1.35rem; text-align: center; }
.signin-tag {
  color: var(--muted); font-size: 0.74rem; text-transform: uppercase;
  letter-spacing: 0.11em; font-weight: 600; margin: 0 0 1.5rem; text-align: center;
  font-family: var(--font-display);
}
.signin-card button { width: 100%; padding: 0.75rem; font-size: 1rem; margin-top: 0.9rem; }

/* ---- Mobile ------------------------------------------------------------- */
@media (max-width: 640px) {
  main { padding: 0 0.75rem; margin-top: 1.1rem; }

  /* The nav used to wrap onto four lines and eat a quarter of the screen
     before a guard saw a single button. One scrollable row instead. */
  .topbar { padding: 0.45rem 0.8rem; gap: 0.5rem; }
  .brand img { height: 32px; }
  .topbar nav {
    order: 3; width: 100%; flex-wrap: nowrap; overflow-x: auto; gap: 1.1rem;
    font-size: 0.9rem; padding-bottom: 0.1rem; scrollbar-width: none;
  }
  .topbar nav::-webkit-scrollbar { display: none; }
  .topbar nav a { white-space: nowrap; }

  /* The alert bells are last in the nav, which on a phone means they start
     off-screen in the scroller — and an unread badge nobody can see is the same
     as no badge. Pinned to the right edge as one cluster they stay put while the
     rest of the nav scrolls under them. The background and the fade are what stop
     items showing through. */
  .nav-alerts {
    position: sticky; right: 0; margin-left: auto; gap: 0.9rem;
    background: var(--command-a); padding-left: 0.7rem;
    box-shadow: -12px 0 10px -6px var(--command-a);
  }
  /* The dividers add nothing in a horizontal scroller — hide them there. */
  .nav-sep { display: none; }
  .userbox { margin-left: auto; font-size: 0.78rem; gap: 0.5rem; }
  .userbox button { padding: 0.35rem 0.7rem; }

  .card.row, .decide { flex-direction: column; align-items: stretch; }
  .decide input { width: 100%; }

  .tapgrid { grid-template-columns: 1fr 1fr; }
  .tap { min-height: 72px; }
  .cprow { max-width: none; }

  /* Five columns don't fit a phone — names and dates wrap mid-word and the
     last column runs off the edge. Each row becomes a labelled stack instead,
     keeping the colour bar that does the ranking. */
  .credhead { display: none; }
  .credrow { grid-template-columns: 1fr; gap: 0.1rem; padding: 0.75rem 0.9rem; }
  .credrow span[data-label]:not(.cred-who)::before {
    content: attr(data-label) " · ";
    font-family: var(--font-display); font-size: 0.65rem;
    text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted);
  }
  .cred-who { font-size: 1rem; font-weight: 600; margin-bottom: 0.15rem; }

  .verify-action input { width: 100%; }
  .verify-action label { flex: 1 1 8rem; }

  /* Left to wrap on its own, the note took a line, Approve took the rest, and
     Deny got orphaned underneath. Note full width, then the two decisions side
     by side where they can be compared. */
  .inbox-actions { flex-direction: column; align-items: stretch; gap: 0.6rem; }
  .inbox-decide { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; width: 100%; }
  .inbox-decide input { grid-column: 1 / -1; width: 100%; }
  .inbox-decide button { min-height: 44px; }
  .inbox-open { margin-left: 0; text-align: center; }

  /* Wide data tables become stacked cards — six columns is unreadable at 375px. */
  table, thead, tbody, th, td, tr { display: block; }
  thead { display: none; }
  table { border: none; background: none; }
  tbody tr {
    background: var(--card); border: 1px solid var(--line);
    border-radius: var(--radius); margin-bottom: 0.6rem; padding: 0.3rem 0.2rem;
    box-shadow: var(--shadow);
  }
  td { border-bottom: none; padding: 0.35rem 0.75rem; }

  /* ...except label/value tables, already two narrow columns and worse stacked. */
  table.pairs { display: table; width: 100%; border: 1px solid var(--line); background: var(--card); }
  table.pairs tbody { display: table-row-group; }
  table.pairs tr { display: table-row; background: none; border: none; margin: 0; padding: 0; box-shadow: none; }
  table.pairs th, table.pairs td {
    display: table-cell; border-bottom: 1px solid var(--line-soft);
    padding: 0.55rem 0.75rem; text-transform: none; letter-spacing: normal;
    font-size: 0.95rem; background: none; font-family: var(--font-ui);
  }
  table.pairs th { color: var(--muted); font-weight: 500; width: 42%; font-size: 0.85rem; }
  table.pairs tr:last-child th, table.pairs tr:last-child td { border-bottom: none; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
  button:active, .tap:active { transform: none; }
}

/* ---- Alarms + live toasts -------------------------------------------- */
.alarm { border-left: 4px solid var(--line); padding-left: 0.8rem; margin: 0.5rem 0; }
.alarm.sev-critical { border-left-color: var(--sev-crit); }
.alarm.sev-high { border-left-color: var(--sev-high); }
.alarm.sev-medium { border-left-color: var(--sev-med); }
.alarm.sev-low    { border-left-color: var(--sev-low); }
.alarm.sev-info   { border-left-color: var(--muted); }

#toast-root {
  position: fixed; right: 1rem; bottom: 1rem; z-index: 1000;
  display: flex; flex-direction: column; gap: 0.5rem; max-width: min(360px, 90vw);
}
.toast {
  background: var(--card); border: 1px solid var(--line); border-left: 4px solid #e67e22;
  border-radius: 8px; padding: 0.7rem 0.9rem; box-shadow: 0 6px 20px rgba(0,0,0,0.18);
  opacity: 0; transform: translateY(12px); transition: opacity 0.3s ease, transform 0.3s ease;
}
.toast.show { opacity: 1; transform: translateY(0); }
.toast.sev-critical { border-left-color: var(--sev-crit); }
.toast.sev-high { border-left-color: var(--sev-high); }
.toast.sev-medium { border-left-color: var(--sev-med); }
.toast.sev-low    { border-left-color: var(--sev-low); }
.toast strong { display: block; }

/* ---- Live KPI panel + charts ---------------------------------------- */
.kpi-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-top: 2rem; }
.kpi-controls { display: flex; align-items: center; gap: 0.5rem; }
.kpi-controls select { width: auto; margin-top: 0; padding: 0.35rem 0.5rem; font-size: 0.85rem; }
.kpi-live { font-size: 0.72rem; font-weight: 600; letter-spacing: 0.06em; color: var(--success); text-transform: uppercase; }
.kpi-live::before { content: ""; }
@keyframes kpi-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
.kpi-live { animation: kpi-pulse 2.2s ease-in-out infinite; }
#kpi-edit { padding: 0.35rem 0.7rem; font-size: 0.82rem; }
.kpi-metricform {
  display: flex; flex-wrap: wrap; gap: 0.45rem 1.3rem;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius-sm);
  box-shadow: var(--shadow); padding: 0.7rem 0.95rem; margin: 0.5rem 0 0.1rem;
}
.kpi-metricform label { display: inline-flex; align-items: center; gap: 0.4rem; margin: 0; font-size: 0.85rem; font-weight: 500; }
.kpi-metricform input { width: auto; margin: 0; }

.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 0.9rem; margin-top: 0.8rem; }
.kpi-card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); padding: 1rem 1.1rem; }
.kpi-trend { grid-column: span 2; }
@media (max-width: 620px) { .kpi-trend { grid-column: auto; } }
.kpi-h { font: 700 0.95rem/1.2 var(--font-display); margin-bottom: 0.55rem; color: var(--ink); }
.kpi-h .muted { font-weight: 400; font-family: var(--font-ui); font-size: 0.8rem; }
.kpi-gauge { margin: 0.2rem 0 0.5rem; }

/* Ring gauge */
.gauge { width: 118px; height: 118px; display: block; margin: 0 auto; }
.gauge-track { stroke: var(--line); }
.gauge-val { transition: stroke-dasharray 0.5s ease; }
.gauge-val.is-ok   { stroke: var(--success); }
.gauge-val.is-warn { stroke: var(--warn); }
.gauge-val.is-crit { stroke: var(--danger); }
.gauge-val.is-none { stroke: var(--faint); }
.gauge-num { font: 700 27px var(--font-display); fill: var(--ink); }
.gauge-sub { font: 600 8.5px var(--font-ui); fill: var(--muted); text-transform: uppercase; letter-spacing: 0.14em; }

/* Vertical bar trend */
.vbars { width: 100%; height: auto; display: block; }
.vbar { fill: var(--brand); transition: fill 0.12s ease; }
.vbar:hover { fill: var(--brand-deep); }
.axisline { stroke: var(--line); stroke-width: 1; }
.vaxis { font: 500 9px var(--font-ui); fill: var(--muted); }

/* Stacked proportional bar */
.segbar { display: flex; gap: 2px; height: 14px; margin: 0.6rem 0 0.5rem; border-radius: 7px; overflow: hidden; background: var(--line-soft); }
.seg { display: block; }
.seg-ok    { background: var(--success); }
.seg-warn  { background: var(--warn); }
.seg-crit  { background: var(--danger); }
.seg-brand { background: var(--brand); }
.seg.is-empty { background: var(--line); }

.kpi-legend { display: flex; flex-wrap: wrap; gap: 0.35rem 1rem; font-size: 0.8rem; color: var(--muted); }
.kpi-legend b { color: var(--ink); font-weight: 600; }
.dot { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 0.35rem; }
.dot.ok { background: var(--success); }
.dot.warn { background: var(--warn); }
.dot.crit { background: var(--danger); }
.dot.brand { background: var(--brand); }

/* Horizontal category bars */
.hbars { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.4rem; }
.hbar { display: grid; grid-template-columns: minmax(70px, 36%) 1fr auto; align-items: center; gap: 0.6rem; font-size: 0.82rem; }
.hbar-lab { color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hbar-track { background: var(--line-soft); border-radius: 6px; height: 10px; overflow: hidden; }
.hbar-fill { display: block; height: 100%; background: var(--brand); border-radius: 6px; transition: width 0.4s ease; }
.hbar-val { color: var(--muted); font-weight: 600; }

/* Stat readout grid */
.kpi-readout { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.8rem 0.6rem; }
.kpi-readout > div { display: flex; flex-direction: column; }
.kpi-readout b { font: 700 1.5rem/1 var(--font-display); color: var(--brand); }
.kpi-readout span { font-size: 0.72rem; color: var(--muted); margin-top: 0.2rem; }
