/* Sandown PD recorder - compliance UI stylesheet.
 *
 * Served from the binary (embed.FS) at /static/app.css. There are deliberately
 * no external fonts, no CDN, and no JavaScript anywhere in this UI: the
 * appliance sits on a police LAN with no internet access, and every action must
 * work with a keyboard and plain HTML alone. Any icon on any page is an inline
 * SVG in the markup, because a stylesheet here may not reference a file at all.
 *
 * WHAT THIS IS SUPPOSED TO LOOK LIKE
 *
 * A municipal operations console: a records system somebody uses for a shift,
 * not a product somebody is being sold. That means density over whitespace,
 * one accent colour rather than a palette, colour reserved for state (held,
 * purged, capture down) instead of decoration, and no motion at all. The
 * hierarchy is carried by weight, size, rules and surfaces, so the page still
 * reads correctly in greyscale and on paper.
 *
 * Layout approach: small-screen first, widened by min-width queries. Written
 * this way round because the failure that matters is a table forcing the page
 * wider than the viewport -- an investigator scrolling sideways to read a
 * caller number, on a laptop in a cruiser, is the case this has to get right.
 * Starting narrow and adding width makes overflow the exception you have to
 * introduce rather than the default you have to remember to prevent.
 *
 * The four breakpoints, and what each is for:
 *
 *   <600px    phone. One column everywhere. Every grid is a stack of cards.
 *   600-1023  tablet. Filters become columns; the small reference tables go
 *             back to being tables; the wide data grids stay stacked.
 *   >=1024    laptop and station PC. Full tables, sticky headings, and tighter
 *             controls because there is a mouse rather than a thumb.
 *   >=1100    the recording detail page splits into two columns.
 *
 * 1024 is where the data grids un-stack because that is the narrowest viewport
 * at which a call row has been measured to fit. At 768 it does not: the page
 * did not overflow, because .table-wrap scrolls, but important actions sat
 * outside the visible box. A grid that does not fit becomes cards; it does not
 * become a horizontal scavenger hunt.
 *
 * Verified against 375, 768, 1024x768, and 1366x768. internal/web has tests
 * asserting the breakpoints, the overflow guards, the contrast of every colour
 * pair below, and that the print rules still restore the table layout.
 *
 * There are no transitions and no animations. This is an evidence tool, not a
 * product tour; a control that moves while you are trying to click it is worse
 * than one that does not. Hover and focus change colour and weight instantly.
 */

:root {
  /* Contrast ratios are against --bg (#ffffff) unless stated, measured, and
     load-bearing: this gets read on a glare-lit desk at 3am.
     internal/web/contrast_test.go recomputes every ratio in this block and
     fails the build if an edit drops one below its stated floor. */
  --ink: #16191d;          /* body text: 15.8:1 */
  --ink-soft: #4a5058;     /* secondary text: 7.6:1 */
  --rule: #c2c8d0;
  --rule-strong: #8d959f;
  --bg: #ffffff;
  --bg-soft: #f3f5f8;
  --bg-sunken: #eaeef3;
  --bg-header: #1f2b3a;
  --accent: #14487a;       /* links: 8.1:1 */
  --accent-dark: #0e3559;
  --accent-soft: #e3edf7;
  --alarm: #a4141c;        /* white on this: 7.3:1 */
  --alarm-ink: #6d0d13;    /* alarm text on --alarm-soft: 8.9:1 */
  --alarm-soft: #fdecec;
  --hold: #7a4a00;         /* white on this: 6.4:1 */
  --hold-ink: #573400;
  --hold-soft: #fdf1de;
  --ok: #1c5c34;
  --ok-ink: #123f23;
  --ok-soft: #e4f2e9;
  --focus: #0a5cc4;

  /* On the dark masthead. Measured against --bg-header rather than white,
     because that is the surface they actually sit on. */
  --on-header: #ffffff;         /* 14.3:1 on --bg-header */
  --on-header-soft: #c8d2de;    /* 9.6:1 on --bg-header */

  --gap: 16px;
  --gap-sm: 8px;
  --gap-lg: 26px;
  --radius: 3px;
  /* Cards and panels. Deliberately small: a 12px corner radius reads as a
     consumer app, and this is a records system. */
  --radius-lg: 5px;
  /* Minimum comfortable hit target. 44px is the accessibility floor; the
     desktop rules tighten it because a mouse is more precise than a thumb. */
  --tap: 44px;

  /* Elevation. Two steps only, both barely there: a surface should read as a
     different plane, not as a floating object. Anything heavier prints as a
     grey smear and reads as decoration on screen. */
  --shadow-sm: 0 1px 2px rgba(22, 25, 29, .07);
  --shadow-md: 0 1px 3px rgba(22, 25, 29, .10), 0 1px 1px rgba(22, 25, 29, .05);

  /* The page frame. --pad is the one gutter value: the masthead, the nav, the
     content and the footer all use it, so the brand, the current tab and the
     page heading sit on the same vertical line at every width. It grows with
     the viewport rather than staying at a phone's 12px on a 1366 screen, where
     edge-to-edge text reads as a debug page rather than a record system. */
  --pad: 12px;
  /* Content stops widening here. Past about 1300px a table row is wider than
     the eye tracks in one pass, and the station PC is 1366: the result is a
     framed page rather than one painted onto the bezel. */
  --shell: 1300px;
  /* Comfortable line length for running prose. Roughly 90 characters. */
  --measure: 68ch;
  /* Same idea for the boxed explanations, which hold two or three sentences
     and should not stretch across a whole 1366 screen either. */
  --measure-wide: 84ch;
}

/* The palette above is a light one and the contrast ratios are measured
   against it. Declaring the scheme stops a browser in dark mode from
   recolouring form controls to something that no longer meets them. */
html { color-scheme: light; }

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

html {
  font-size: 100%;
  /* Belt and braces against a single wide element scrolling the whole page. */
  overflow-x: hidden;
}

body {
  margin: 0;
  background: var(--bg-soft);
  color: var(--ink);
  /* Segoe UI first so a Windows dev box renders what it always did; system-ui
     behind it so Debian picks its own UI face rather than falling through to
     DejaVu's wider metrics. No webfont: there is nothing to fetch it from. */
  font: 15px/1.5 "Segoe UI", system-ui, "DejaVu Sans", Arial, sans-serif;
  /* A 64-character hash or a pasted URL in a note must wrap, not widen. */
  overflow-wrap: break-word;
  -webkit-text-size-adjust: 100%;
  /* Push the footer to the bottom of the window on a short page. The sign-in
     screen and the "not permitted" page are both a few lines long, and a
     footer rule floating in the middle of an otherwise blank screen reads as a
     page that failed to finish loading. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
/* Column flex will shrink items to fit min-height; the bands are content-sized
   and must not be squeezed on a long page. */
.masthead, .mainnav, .site-footer { flex-shrink: 0; }

/* ---------- skip link + focus ---------- */

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--bg);
  color: var(--accent);
  padding: 10px 16px;
  border: 2px solid var(--focus);
  font-weight: 600;
  z-index: 100;
}
.skip-link:focus { left: 0; }

a { color: var(--accent); }
a:hover { text-decoration: none; }

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}
/* Fallback for engines without :focus-visible - an older station PC must still
   show a focus ring, even if it shows it more often than strictly needed. */
@supports not selector(:focus-visible) {
  a:focus, button:focus, input:focus, select:focus, textarea:focus, summary:focus {
    outline: 3px solid var(--focus);
    outline-offset: 2px;
  }
}

/* ---------- iconography ----------
 *
 * Every icon on this site is an inline SVG in the markup, drawn in
 * currentColor and marked aria-hidden. Two consequences worth stating: the
 * stylesheet references no file, which is what keeps the appliance's
 * no-internet guarantee cheap; and an icon is never the only carrier of
 * meaning, because it is invisible to a screen reader by construction. Colour
 * and shape are always accompanied by a word.
 */
.icon {
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
  vertical-align: -0.125em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ---------- masthead + nav ---------- */

/* The shared content frame. The coloured bands (masthead, nav, footer) stay
   full width so the page still reads as an application chrome; what sits
   inside them lines up with the content column. */
.shell {
  width: 100%;
  max-width: var(--shell);
  margin-left: auto;
  margin-right: auto;
  min-width: 0;
}

.masthead {
  background: var(--bg-header);
  color: var(--on-header);
  padding: 10px var(--pad);
  border-bottom: 3px solid #0d1620;
}
.masthead-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
}
.masthead .brand {
  display: flex;
  align-items: center;
  gap: 9px;
  font-weight: 700;
  font-size: 16px;
  letter-spacing: .01em;
  min-width: 0;
}
.masthead .brand a { color: var(--on-header); text-decoration: none; }
.masthead .brand a:hover { text-decoration: underline; }
/* The mark. Sized in px rather than em so it stays the same object whatever
   the brand text does at a given breakpoint. */
.brandmark {
  width: 30px;
  height: 30px;
  flex: 0 0 auto;
  object-fit: contain;
  filter: drop-shadow(0 3px 5px rgb(0 0 0 / 35%));
}
.masthead .spacer { flex: 1 1 auto; }
.masthead .whoami {
  font-size: 14px;
  color: var(--on-header-soft);
  /* On a phone the identity line goes under the title rather than squeezing
     the sign-out button off the edge. */
  flex: 1 1 100%;
  order: 3;
  /* Usernames here are department account names, not display names, and some
     of them are long. This line must wrap or break rather than push the sign
     out button off the edge of the masthead. */
  min-width: 0;
  overflow-wrap: anywhere;
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
}
.masthead .whoami strong { color: var(--on-header); font-weight: 600; }
.masthead form { margin: 0; flex: 0 0 auto; }
.masthead .btn, .masthead button {
  min-height: 36px;
}
/* The signed-in role, as a token rather than a parenthetical. Three roles that
   differ only in what they may do are worth being able to read at a glance --
   and on a shared station PC, "which account am I in" is a question people
   should not have to answer by trying something and being refused. */
.role-tag {
  display: inline-block;
  padding: 1px 7px;
  border: 1px solid var(--on-header-soft);
  border-radius: 9px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--on-header);
  white-space: nowrap;
}

.mainnav {
  background: var(--bg);
  border-bottom: 1px solid var(--rule);
  padding: 0 var(--pad);
  box-shadow: var(--shadow-sm);
}
.mainnav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
}
.mainnav li { margin: 0; flex: 1 1 auto; }
.mainnav a {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 12px 14px;
  min-height: var(--tap);
  color: var(--ink-soft);
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  border-bottom: 3px solid transparent;
  white-space: nowrap;
}
.mainnav a:hover { background: var(--bg-soft); color: var(--accent); }
.mainnav a[aria-current="page"] {
  border-bottom-color: var(--accent);
  background: var(--bg);
  color: var(--accent-dark);
  font-weight: 700;
}
.mainnav .icon { width: 16px; height: 16px; }

/* ---------- layout ---------- */

main {
  padding: var(--gap) var(--pad);
  /* width AND max-width. main is a flex item in the body's column, and a flex
     item with auto cross-axis margins is not stretched to the line -- it is
     sized to its content. Without the explicit 100% the page silently became
     as wide as its widest table, so a 1024px laptop rendered a 666px column
     floating in the middle of the window. */
  width: 100%;
  max-width: calc(var(--shell) + 2 * var(--pad));
  margin-left: auto;
  margin-right: auto;
  /* Takes the slack in that column, which is what holds the footer to the
     bottom of the window on a short page. */
  flex: 1 0 auto;
}

/* The page heading block: what this screen is, one sentence on what it records,
   and any page-level action. Ruled off underneath so the first thing below it
   reads as content rather than as more heading. */
.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 6px var(--gap);
  padding-bottom: 12px;
  margin-bottom: var(--gap);
  border-bottom: 2px solid var(--rule);
}
.page-head > .page-headings { flex: 1 1 22rem; min-width: 0; }
.page-head .page-intro { margin-bottom: 0; }
.page-head .head-actions {
  display: flex;
  gap: var(--gap-sm);
  flex-wrap: wrap;
  align-items: center;
}

h1 {
  font-size: 21px;
  line-height: 1.25;
  letter-spacing: -.005em;
  margin: 0 0 4px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}
h2 {
  font-size: 17px;
  margin: var(--gap-lg) 0 10px;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 5px;
}
h3 { font-size: 15px; margin: 18px 0 6px; }
/* A small capitalised label above a group of facts or a card's contents. Does
   the job a heading would without adding a level to the document outline. */
.eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 6px;
}
.page-intro {
  color: var(--ink-soft);
  margin: 0 0 var(--gap);
  /* Page intros explain what a screen records and who can see it. On a 1366
     screen an uncapped one is a single 180-character line, which is the width
     at which people stop reading the sentence that matters most. */
  max-width: var(--measure);
}
/* Running prose and reference lists that sit directly on a page get the same
   treatment. Data -- tables, banners, forms -- deliberately does not: an alarm
   or an evidence grid should use the width it has. */
main > p, main > ul, main > ol { max-width: var(--measure-wide); }

.site-footer {
  margin-top: 32px;
  padding: 14px var(--pad);
  border-top: 1px solid var(--rule);
  background: var(--bg);
  color: var(--ink-soft);
  font-size: 13px;
}
.site-footer .shell { display: flex; gap: 8px; align-items: flex-start; }

/* ---------- surfaces ----------
 *
 * One card treatment, used for every panel on the site: a white surface on the
 * page's soft grey, a hairline border, and the smaller of the two shadows. The
 * border rather than the shadow is what defines the edge, so the card survives
 * greyscale, high-contrast mode and the printer.
 */
.card {
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 14px;
  margin: 0 0 var(--gap);
  min-width: 0;
}
.card > :first-child { margin-top: 0; }
.card > :last-child { margin-bottom: 0; }
.card > h2, .card > h3 {
  margin-top: 0;
  border-bottom: 0;
  padding-bottom: 0;
}
/* A card holding running prose rather than data keeps a readable measure. The
   main > p rule cannot reach inside a card, and a three-line list stretched
   across 1300px is the thing that rule exists to prevent. */
.card.prose { max-width: var(--measure-wide); }
/* Content that sits directly on the page rather than in a card still needs to
   read as a surface where it holds data. */
.panel {
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  min-width: 0;
}

/* ---------- banners and messages ---------- */

.banner {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  margin: 0 0 14px;
  border: 1px solid;
  border-left: 6px solid;
  border-radius: var(--radius);
  font-weight: 600;
}
.banner .icon { width: 20px; height: 20px; margin-top: 1px; }
.banner > div { min-width: 0; flex: 1 1 auto; }
.banner-alarm  { background: var(--alarm-soft); border-color: var(--alarm); color: var(--alarm-ink); }
.banner-error  { background: var(--alarm-soft); border-color: var(--alarm); color: var(--alarm-ink); }
.banner-notice { background: var(--ok-soft);    border-color: var(--ok);    color: var(--ok-ink); }
.banner-warn   { background: var(--hold-soft);  border-color: var(--hold);  color: var(--hold-ink); }
.banner p { margin: 6px 0 0; font-weight: 400; }

/* ---------- empty / placeholder ---------- */

.empty {
  padding: 18px;
  background: var(--bg);
  border: 1px dashed var(--rule-strong);
  border-radius: var(--radius-lg);
  color: var(--ink-soft);
  max-width: var(--measure-wide);
}
.empty p { margin: 0 0 8px; }
.empty p:last-child { margin-bottom: 0; }
.empty strong { color: var(--ink); }

/* ---------- stat tiles ----------
 *
 * The overview's three retention states. This was a three-column table, which
 * gave the count, the label and the explanation equal weight -- and the count
 * is the only part anybody scans for. As tiles the number is the object and
 * the sentence underneath it is available when wanted.
 */
.statgrid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--gap);
  margin: 0 0 var(--gap);
}
.stat {
  display: flex;
  flex-direction: column;
  gap: 6px;
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 14px;
  min-width: 0;
}
.stat .stat-value {
  font-size: 30px;
  line-height: 1.05;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  color: var(--ink);
  text-decoration: none;
}
a.stat-value:hover { color: var(--accent-dark); text-decoration: underline; }
.stat .stat-note { font-size: 13px; color: var(--ink-soft); margin: 0; }
/* A thin state stripe down the left edge, so the three tiles are told apart by
   position and colour as well as by their labels. */
.stat-active { border-left: 4px solid var(--ok); }
.stat-held   { border-left: 4px solid var(--hold); }
.stat-purged { border-left: 4px solid var(--rule-strong); }

/* ---------- forms ---------- */

fieldset {
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  background: var(--bg);
  padding: 12px 14px 14px;
  margin: 0 0 var(--gap);
  /* A form is not a data grid. Left uncapped, the case-reference box on the
     export page was a 1300px rectangle around one 480px input, which reads as
     a mistake rather than a group. */
  max-width: var(--measure-wide);
  /* A fieldset is a replaced-ish element in some engines and will not shrink
     below its content without this; a wide filter row would push the page. */
  min-width: 0;
}
legend { font-weight: 700; padding: 0 6px; }

/* The filter panel is a <details>, shipped open.
 *
 * Eight fields is most of a phone screen, and on a narrow window that put the
 * results below the fold on every visit. A disclosure lets someone collapse it
 * once they have their filters set and keep the list in view. It ships OPEN
 * rather than closed because there is no JavaScript here to remember a
 * preference, and a search page whose filters are hidden by default on a
 * desktop would be worse than one that takes a scroll on a phone.
 */
.filterbox {
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  /* A tinted panel, so the controls that shape the list read as one group and
     the results below them are unmistakably a different thing. The inputs stay
     white against it, which is the whole point -- on a plain white page a row
     of eight fields and a table look like the same surface. */
  background: var(--bg-soft);
  padding: 0 14px 14px;
  margin: 0 0 var(--gap);
  min-width: 0;
}
/* Deliberately NOT display:flex. Setting any display value other than
   list-item on a <summary> removes the browser's own disclosure triangle, and
   that triangle is the only thing on the page saying the panel can be
   collapsed -- there is no JavaScript here to add one back. The chip below is
   an inline-block instead, which needs no flex container. */
.filterbox > summary {
  font-weight: 700;
  padding: 12px 0;
  min-height: var(--tap);
  cursor: pointer;
  list-style-position: inside;
}
.filterbox > summary:hover { color: var(--accent); }
.filterbox[open] > summary { border-bottom: 1px solid var(--rule); margin-bottom: 14px; }
/* Says, in the summary itself, that the list below is not the whole set. When
   the panel is collapsed this is the only thing on screen that would. */
.filterbox .filter-state {
  display: inline-block;
  margin-left: 8px;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--accent-dark);
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  border-radius: 9px;
  padding: 1px 8px;
}

/* The filter row: a grid that reflows by available width rather than a set of
   inline-blocks that wrap into ragged rows. */
.filters {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px 14px;
  /* Top, not bottom. Bottom-aligning looks tidier right up until one field in
     the row carries a hint under its control -- the "Add an account" form has
     one -- and then that field's input rides up while its neighbours sit down,
     and the row is a staircase. Aligning the labels is what the eye reads
     across anyway. */
  align-items: start;
}

/* Two fields per row once a native date input still fits beside another one.
   Below this the panel stays single-column: a clipped date picker is worse
   than a taller form. */
@media (min-width: 480px) {
  .filters { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* On a narrow screen the open filter panel is most of the first screenful, so
   offer a way past it. Hidden at and above the stacking breakpoint, where the
   results are already a table and visible without scrolling. Kept in step with
   that breakpoint deliberately: while the list is a stack of cards, the panel
   plus the first card is more than a screenful. */
.jump-to-results {
  display: inline-block;
  margin: 0 0 12px;
  padding: 8px 0;
  font-weight: 600;
}
@media (min-width: 1024px) {
  .jump-to-results { display: none; }
}
.field { min-width: 0; }
.field label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
  margin-bottom: 3px;
}
.field-block { display: block; }
/* A one-line answer does not need a 1300px box. A case reference is eight
   characters; a note is a paragraph. Capping them keeps a form looking like a
   form instead of a row of empty banners. */
.field-block input { max-width: 30rem; }
.field-block textarea { max-width: 46rem; }
.hint { display: block; font-size: 13px; color: var(--ink-soft); margin-top: 4px; }
.req { color: var(--alarm); font-weight: 700; }

input[type=text], input[type=password], input[type=date],
input[type=number], input[type=search], select, textarea {
  font: inherit;
  width: 100%;
  min-width: 0;
  padding: 9px 8px;
  min-height: var(--tap);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: #fff;
  color: var(--ink);
}
input[type=text]:hover, input[type=password]:hover, input[type=date]:hover,
input[type=number]:hover, input[type=search]:hover, select:hover, textarea:hover {
  border-color: var(--ink-soft);
}
textarea { min-height: 4.5em; line-height: 1.4; }

/* Invalid-field styling, and when it is allowed to appear.
 *
 * :invalid alone matches a required field the moment the page loads, so
 * "Reason for retaining" and "Case reference" opened outlined in alarm red
 * before anyone had touched them. On a system whose red means "capture is
 * down" and "this export was refused", spending that colour on a field the
 * operator has not reached yet is how the colour stops meaning anything.
 *
 * :user-invalid only matches after the field has been edited or submitted,
 * which is the moment there is actually something wrong. Where it is not
 * supported the rule below is inert and the older :invalid behaviour stands --
 * noisy, but never silent about a genuine error. */
input:invalid, textarea:invalid { border-color: var(--alarm); }
@supports selector(:user-invalid) {
  input:invalid, textarea:invalid { border-color: var(--rule-strong); }
  input:user-invalid, textarea:user-invalid { border-color: var(--alarm); }
}

button, .btn {
  font: inherit;
  font-weight: 600;
  padding: 10px 16px;
  min-height: var(--tap);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  text-align: center;
}
button:hover, .btn:hover { background: var(--accent-dark); border-color: var(--accent-dark); }
button:active, .btn:active { background: var(--accent-dark); }
button.secondary, .btn.secondary {
  background: #fff;
  color: var(--accent-dark);
  box-shadow: var(--shadow-sm);
}
button.secondary:hover, .btn.secondary:hover { background: var(--accent-soft); border-color: var(--accent-dark); }
button.danger, .btn.danger { background: var(--alarm); border-color: var(--alarm); color: #fff; }
button.danger:hover, .btn.danger:hover { background: #7d0f16; border-color: #7d0f16; }
button[disabled], .btn[disabled], button:disabled {
  background: var(--bg-sunken);
  border-color: var(--rule-strong);
  color: var(--ink-soft);
  cursor: not-allowed;
  box-shadow: none;
}
.masthead button.secondary {
  background: transparent;
  color: var(--on-header);
  border-color: var(--on-header-soft);
  box-shadow: none;
}
.masthead button.secondary:hover { background: #33475d; border-color: var(--on-header); }

.inline-form { display: inline-block; margin: 0; }

/* The per-row "change this account's role" control.
 *
 * Its label has to stay visible -- eight rows of identical <select> elements
 * need to say whose role each one sets -- but the select and its button belong
 * on one line. Left to the defaults the select took the full cell width and
 * pushed the button onto a third line, which made every row on the users page
 * three lines tall for one dropdown. */
.role-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
}
.role-form > label { flex: 1 1 100%; margin: 0; }
.role-form select { flex: 1 1 9rem; width: auto; min-width: 9rem; }
.role-form button { flex: 0 0 auto; }
.actions {
  margin-top: 14px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
}
/* On a narrow screen the primary action gets the full width rather than
   sharing a line with Cancel, so a thumb cannot hit the wrong one. */
.actions > button, .actions > .btn { flex: 1 1 auto; }

/* ---------- tables ---------- */

/* Every grid is wrapped in one of these. A table that cannot shrink below its
   content scrolls inside its own box instead of widening the page -- and it is
   focusable and labelled, so the scroll is reachable from the keyboard. */
.table-wrap {
  overflow-x: auto;
  max-width: 100%;
  -webkit-overflow-scrolling: touch;
}

table.grid {
  border-collapse: collapse;
  width: 100%;
  font-size: 14px;
  background: var(--bg);
}
table.grid caption {
  text-align: left;
  padding: 6px 0 8px;
  color: var(--ink-soft);
  font-size: 13px;
}
table.grid th, table.grid td {
  border: 1px solid var(--rule);
  padding: 8px;
  text-align: left;
  vertical-align: top;
}
.player-duration {
  margin: 0 0 8px;
  font-variant-numeric: tabular-nums;
}
/* Column headings read as labels rather than as a first row of data: smaller,
   capitalised and tracked out, which is the convention every operations
   console uses and the reason a dense grid stays scannable at 14px. */
table.grid thead th {
  background: var(--bg-soft);
  border-bottom: 2px solid var(--rule-strong);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
}
table.grid tbody tr:nth-child(even) { background: #fafbfd; }
table.grid tbody tr:hover { background: var(--accent-soft); }
table.grid tbody th[scope="row"] { font-weight: 600; }
/* These carry the table.grid prefix so they outrank the generic cell rule
   above them. Without it `table.grid td` (two classes deep) beat a bare
   `td.num`, and the right alignment this class exists for silently never
   happened: a column of call durations rendered ragged-left in every list and
   on every printed page. */
table.grid td.num, table.grid th.num { text-align: right; font-variant-numeric: tabular-nums; }
table.grid td.when, table.grid th.when { white-space: nowrap; font-variant-numeric: tabular-nums; }
/* Audit detail and note bodies are unbounded text and must not set the width
   of the table on their own. */
td.wrap { overflow-wrap: anywhere; min-width: 12ch; }

/* A cell whose value is more than one thing -- a purge date and a countdown
 * pill, a username and "(you)", a "no" and the sentence explaining it -- wraps
 * them in this.
 *
 * It does nothing at all in a table. It matters in the stacked card layout
 * below, where the cell becomes a two-column grid: without a wrapper, the
 * label takes column one and EACH remaining run of content becomes its own
 * grid item, so the second half of a value wrapped onto the next row and
 * landed underneath the label. One wrapper, one grid item, one value.
 *
 * internal/web/responsive_test.go asserts no body cell ever has more than one
 * top-level piece of content, which is what stops this being remembered only
 * by whoever last read this comment. */
.cellval { display: inline; }

/* Stacked layout for anything narrower than a laptop.
 *
 * Below 1024px an eight-column call list cannot be read as a grid at any font
 * size worth having, so each row becomes a labelled block. The labels come
 * from data-label on the cell, which the templates set from the same text as
 * the column header.
 *
 * The boundary is 1024 and not 768 because the call list carries timestamps,
 * retention state, duration and two explicit actions. At 768 the page did not
 * overflow -- the .table-wrap did its job -- but important actions sat outside
 * the visible part of that box. A control you have to scroll a nested region
 * to reach is not a control an operator will find at 3am.
 *
 * Small reference tables are exempt from 600px up; see .grid.compact below.
 *
 * Changing display on table elements drops their implicit ARIA roles in most
 * engines, so the markup carries explicit role="table"/"row"/"cell" attributes
 * and the semantics survive the reflow. The header row is hidden visually and
 * from assistive technology together, because with per-cell labels it would
 * otherwise be announced twice.
 */
@media (max-width: 1023px) {
  table.grid { border: 0; }
  table.grid thead { display: none; }
  table.grid tbody, table.grid tr, table.grid th, table.grid td { display: block; }
  table.grid tr {
    border: 1px solid var(--rule-strong);
    border-radius: var(--radius-lg);
    margin-bottom: 14px;
    padding: 2px 0;
    background: var(--bg);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
  }
  table.grid tbody tr:nth-child(even) { background: var(--bg); }
  table.grid tbody tr:hover { background: var(--bg); }
  table.grid th, table.grid td {
    border: 0;
    border-bottom: 1px solid var(--rule);
    display: grid;
    grid-template-columns: minmax(7rem, 38%) minmax(0, 1fr);
    gap: 4px 12px;
    /* Top, not baseline: retention stacks and two-part action summaries are
       taller than their labels and should begin on the same line. */
    align-items: start;
    text-align: left;
  }
  table.grid tr > *:last-child { border-bottom: 0; }
  table.grid td::before,
  table.grid th[data-label]::before {
    content: attr(data-label);
    font-weight: 700;
    font-size: 11.5px;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  /* A row header carries the row's identity; give it the emphasis the header
     row used to, and the card's own top edge. */
  table.grid tbody th[scope="row"] {
    font-size: 15px;
    background: var(--bg-soft);
    border-bottom-color: var(--rule-strong);
  }
  /* A cell is a grid container here, so anything inside it is a grid item and
     is stretched to the column by default. A status pill stretched to 60% of a
     card is a coloured bar with a word in it, not a badge -- and the whole
     reason it is a pill is that it should be readable as one glance-sized
     token. Same for the buttons, which are inline-flex and blockify. */
  table.grid td > .pill,
  table.grid th > .pill,
  table.grid td > .compact-action,
  table.grid th > .compact-action { justify-self: start; }
  /* A .hint carries its form-field top margin with it. As the whole value of a
     stacked cell -- "never", "(you)" -- that margin drops the value a few
     pixels below the label it belongs to. */
  table.grid td > .hint,
  table.grid th > .hint { margin-top: 0; }
  table.grid td.num, table.grid th.num { text-align: left; }
  table.grid td.when, table.grid th.when { white-space: normal; }
}

/* The exemption.
 *
 * Two tables on this site are small, static reference material rather than
 * evidence: the retention-state key on the overview and the role matrix on the
 * users page. Three or four short columns fit a tablet comfortably, and
 * stacking them turns a three-row key into three tall cards for no gain. They
 * still stack below 600px, where nothing survives as a table.
 *
 * Scoped to screen so the print block below stays the single authority on what
 * a printed grid looks like. */
@media screen and (min-width: 600px) and (max-width: 1023px) {
  table.grid.compact thead { display: table-header-group; }
  table.grid.compact tbody { display: table-row-group; }
  table.grid.compact tr {
    display: table-row;
    border: 0;
    border-radius: 0;
    margin: 0;
    padding: 0;
    box-shadow: none;
  }
  table.grid.compact th, table.grid.compact td {
    display: table-cell;
    border: 1px solid var(--rule);
    padding: 8px;
    vertical-align: top;
  }
  table.grid.compact thead th {
    background: var(--bg-soft);
    border-bottom: 2px solid var(--rule-strong);
  }
  table.grid.compact tbody th[scope="row"] { background: none; }
  table.grid.compact td::before,
  table.grid.compact th[data-label]::before { content: none; }
  table.grid.compact tbody tr:nth-child(even) { background: #fafbfd; }
  table.grid.compact tbody tr:hover { background: var(--accent-soft); }
  table.grid.compact td.num, table.grid.compact th.num { text-align: right; }
}

/* ---------- status pills ---------- */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px;
  border: 1px solid;
  border-radius: 10px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .03em;
  white-space: nowrap;
}
/* A filled dot in the pill's own colour. Shape as well as hue, so the three
   states are still distinguishable to someone who cannot separate the reds and
   greens, and so a photocopied page keeps the distinction. */
.pill::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}
.pill-active { background: var(--ok-soft);    border-color: var(--ok);          color: var(--ok-ink); }
.pill-held   { background: var(--hold-soft);  border-color: var(--hold);        color: var(--hold-ink); }
.pill-purged { background: var(--bg-sunken);  border-color: var(--rule-strong); color: var(--ink-soft); }
.pill-soon   { background: var(--alarm-soft); border-color: var(--alarm);       color: var(--alarm-ink); }

/* ---------- detail layout ---------- */

.detail-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px 24px;
}
.detail-col { min-width: 0; }
/* The first heading in a column should not push away from the one above it. */
.detail-col > h2:first-child { margin-top: 8px; }

dl.facts {
  margin: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 2px 14px;
}
dl.facts dt {
  color: var(--ink-soft);
  font-size: 13px;
  font-weight: 600;
}
dl.facts dd {
  margin: 0 0 8px;
  overflow-wrap: anywhere;
}

.hash {
  font-family: "Cascadia Mono", Consolas, "DejaVu Sans Mono", monospace;
  font-size: 12.5px;
  overflow-wrap: anywhere;
  line-height: 1.7;
  background: var(--bg-soft);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 8px;
  margin: 0;
}

.callout {
  background: var(--bg-soft);
  border: 1px solid var(--rule);
  border-left: 4px solid var(--rule-strong);
  border-radius: var(--radius);
  padding: 12px 14px;
  margin: 12px 0;
  /* A callout is two or three sentences of explanation. Stretched across a
     1366px screen it stops being a note and becomes a wall; inside a detail
     column this cap never binds. Banners deliberately do NOT get this -- an
     alarm should take all the width it can. */
  max-width: var(--measure-wide);
}
.callout p { margin: 0 0 8px; }
.callout p:last-child { margin-bottom: 0; }
.callout-warn { background: var(--hold-soft); border-left-color: var(--hold); border-color: #e6d3b0; }
.callout-danger { background: var(--alarm-soft); border-left-color: var(--alarm); border-color: #e8b6b9; color: var(--alarm-ink); }

audio {
  width: 100%;
  max-width: 480px;
  vertical-align: middle;
}

/* Search results deliberately do not squeeze a browser-native media widget
   into a table cell. The known CDR duration is available without touching the
   evidence file; the full player lives on the recording page where it has the
   width and integrity context it needs. */
.retention-stack,
.time-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 5px;
  min-width: 0;
}
.duration {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  font-weight: 760;
  letter-spacing: .02em;
}
.time-secondary {
  color: var(--ink-soft);
  font-size: .78rem;
  line-height: 1.25;
  white-space: normal;
}
.compact-action {
  min-height: 34px;
  padding: 6px 10px;
  white-space: nowrap;
}
/* The player on the detail page is the primary control on that screen; give it
   a surface of its own so it does not read as a stray browser widget dropped
   onto the page. */
.player {
  background: var(--bg-soft);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  padding: 12px;
  margin: 0 0 12px;
}
.player audio { display: block; }
.player .hint { margin-top: 8px; margin-bottom: 0; }

.timeline { list-style: none; margin: 0; padding: 0; }
.timeline li {
  padding: 8px 0 8px 14px;
  border-bottom: 1px solid var(--rule);
  border-left: 3px solid var(--rule);
  overflow-wrap: anywhere;
}
.timeline li:last-child { border-bottom: 0; }
.timeline .meta { color: var(--ink-soft); font-size: 13px; margin-top: 2px; }

/* ---------- pager ---------- */

.pager {
  margin: 12px 0;
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}
.pager .count { flex: 1 1 100%; order: -1; color: var(--ink-soft); }
.pager .btn { flex: 1 1 auto; }
.pager [aria-disabled="true"] {
  color: var(--ink-soft);
  border-color: var(--rule);
  background: var(--bg-soft);
  box-shadow: none;
  pointer-events: none;
}

/* ---------- sign-in and step-up ---------- */

.signin {
  max-width: 26rem;
  margin: 6vh auto;
  padding: 0 12px;
}
.signin .card { padding: 22px 20px; box-shadow: var(--shadow-md); }
.signin h1 { margin-bottom: 6px; }
.signin .page-intro { margin-bottom: 18px; }
.signin .field { margin-bottom: 12px; }
.signin button { width: 100%; margin-top: 8px; }
/* The lock-up above the sign-in form: the mark, the department, the system. */
.signin-brand {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}
.signin-brand .brandmark { width: 58px; height: 58px; }
.signin-brand .org {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.signin-note {
  margin: 16px 0 0;
  font-size: 13px;
  color: var(--ink-soft);
}
/* The re-authentication prompt sits inside the page rather than replacing it,
   so what is about to happen stays on screen beside the password box. */
.stepup { max-width: 34rem; }

/* ==========================================================================
   Wider viewports.
   ========================================================================== */

/* Tablet and up: the filter row becomes a grid of real columns and the page
   gets its side padding back. */
@media (min-width: 600px) {
  :root { --pad: 20px; }
  .masthead { padding: 9px var(--pad); }
  /* The identity line can share the top row again, but it is still the thing
     that gives way: it shrinks and wraps before the sign-out button moves. */
  .masthead .whoami { flex: 0 1 auto; order: 0; justify-content: flex-end; }
  .mainnav li { flex: 0 0 auto; }
  .mainnav a { text-align: left; justify-content: flex-start; }
  /* Pull the tabs out by their own padding so the tab LABEL, not its box,
     lines up with the brand above it and the page heading below it. */
  .mainnav ul { margin-left: -14px; margin-right: -14px; }
  .filters { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
  .actions > button, .actions > .btn { flex: 0 0 auto; }
  .pager .count { flex: 0 1 auto; order: -1; }
  .pager .btn { flex: 0 0 auto; }
  .statgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  dl.facts { grid-template-columns: max-content minmax(0, 1fr); }
  dl.facts dd { margin-bottom: 2px; }
  .signin { margin: 8vh auto; }
  .card { padding: 16px 18px; }
}

/* Laptop and up (1024x768 is the low end of what is on a desk at the station).
   Controls tighten because there is a mouse; the 44px floor was for thumbs. */
@media (min-width: 1024px) {
  :root { --tap: 34px; --pad: 28px; }
  /* Eight- and nine-column evidence tables need their full actions visible at
     a 1280px laptop width. The former 10px horizontal padding alone added
     36px to a nine-column row and forced a pointless horizontal nudge. */
  table.grid th, table.grid td { padding: 7px 8px; }
  input[type=text], input[type=password], input[type=date],
  input[type=number], input[type=search], select { padding: 6px 8px; }
  button, .btn { padding: 7px 14px; }

  /* A little more air between the heading, the sentence under it, and the
     first thing on the page. At this width the page is a document, and the
     phone-tight spacing that keeps a call in view on a 375px screen reads as
     cramped on a 1366 one. */
  h1 { font-size: 25px; }
  h2 { font-size: 18px; margin-top: 30px; }
  .page-intro { margin-bottom: 22px; }
  .page-head { padding-bottom: 14px; margin-bottom: 22px; }
  .page-head .page-intro { margin-bottom: 0; }
  main { padding-top: 22px; }

  /* Long lists are the normal case here, and losing the column headings after
     twenty rows is what makes a dense table hard to read. */
  table.grid thead th {
    position: sticky;
    top: 0;
    z-index: 1;
  }
}

/* 1366x768 station PC: two columns on the recording detail page, so playback,
   call facts, and the integrity hash sit beside retention and history without
   scrolling. */
@media (min-width: 1100px) {
  .detail-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    /* Enough gutter that "Call details" on the left and "Retention" on the
       right read as two columns of a document rather than one wrapped one. */
    column-gap: 44px;
  }
}

/* ---------- print ----------
 *
 * Call lists get printed into case files, so the printed page is a real
 * output, not an afterthought.
 */
@media print {
  :root { --ink: #000; --ink-soft: #333; --rule: #999; }
  html { overflow-x: visible; }
  /* Back to normal flow: the screen's flex column exists to pin a footer to
     the bottom of a window, and paper has no window. */
  body {
    font-size: 11pt;
    color: #000;
    background: #fff;
    display: block;
    min-height: 0;
  }

  .masthead, .mainnav, .site-footer, .skip-link,
  form, .filterbox, .actions, .pager, audio,
  .jump-to-results, .no-print { display: none !important; }

  /* The capture-health alarm must survive onto paper: a printed call list from
     a period when capture was down should say so on its face. */
  .capture-banner {
    display: block !important;
    border: 2px solid #000;
    background: #fff;
    color: #000;
  }
  /* An icon is a hairline stroke in currentColor; on paper it is either
     invisible or a smudge, and the word beside it says the same thing. */
  .icon, .brandmark { display: none !important; }

  main { padding: 0; max-width: none; }
  .page-intro, main > p, main > ul, main > ol { max-width: none; }
  .page-head { border-bottom: 1px solid #000; margin-bottom: 10pt; }
  h1 { font-size: 15pt; }
  h2 { font-size: 12pt; border-bottom: 1px solid #000; }

  /* Surfaces are a screen idea. On paper a card is a rule and a heading. */
  .card, .panel, .stat, .player {
    border: 1px solid #666;
    border-radius: 0;
    box-shadow: none;
    background: #fff;
    padding: 6pt;
  }
  .statgrid { display: block; }

  /* Undo the narrow-screen stacking: a printer is not a phone, and the browser
     lays paper out at roughly 816px, which is inside the range where the
     screen rules turn every grid into cards. Each of these undoes one of those
     rules -- including the card's own border, margin and padding, which do not
     show up as a display value but do show up as a box drawn round every
     single row of a printed call list. */
  .table-wrap { overflow: visible; }
  table.grid { border-collapse: collapse; font-size: 9.5pt; }
  table.grid thead { display: table-header-group; }  /* repeat headers per page */
  table.grid tbody { display: table-row-group; }
  table.grid tr {
    display: table-row;
    page-break-inside: avoid;
    border: 0;
    border-radius: 0;
    margin: 0;
    padding: 0;
    box-shadow: none;
    overflow: visible;
  }
  table.grid th, table.grid td {
    display: table-cell;
    border: 1px solid #666;
    padding: 3px 5px;
  }
  table.grid td::before, table.grid th[data-label]::before { content: none; }
  table.grid thead th { position: static; background: #fff; color: #000; }
  table.grid tbody th[scope="row"] { background: #fff; font-size: 9.5pt; }
  table.grid tbody tr:nth-child(even) { background: #fff; }
  table.grid tbody tr:hover { background: #fff; }
  /* The screen's stacked layout left-aligns these because a card has no
     columns to align to. On paper the columns are back. */
  table.grid td.num, table.grid th.num { text-align: right; }
  table.grid td.when, table.grid th.when { white-space: nowrap; }
  /* Playback and navigation actions have no useful paper behavior. Hiding
     their headers and cells together keeps the remaining columns aligned. */
  table.grid th.no-print, table.grid td.no-print { display: none !important; }

  .detail-grid { display: block; }
  dl.facts { grid-template-columns: max-content 1fr; }
  .pill { border: 1px solid #000; background: #fff; color: #000; }
  /* The dot is the part of a pill that survives a photocopier and a reader who
     cannot separate the reds and greens, and it is drawn with a background --
     which every browser drops from a printed page unless it is told not to.
     Without this the printed pill is an outlined word with a blank space in
     front of it, which is not what the design says it prints as. Scoped to the
     dot alone: this is a 7px disc, not a licence to print filled surfaces. */
  .pill::before {
    background: #000;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
  .callout, .empty { border: 1px solid #666; background: #fff; }
  .hash { background: #fff; border: 1px solid #666; }
  .timeline li { border-left: 1px solid #666; }

  /* Keep a heading with the content it introduces. */
  h1, h2, h3 { page-break-after: avoid; }
  .timeline li { page-break-inside: avoid; }

  a { color: #000; text-decoration: underline; }
  /* Printed links to this appliance are useless on paper; the ID beside them
     is what a reader needs. */
  a[href]::after { content: none; }
}

/* Somebody, someday, will turn on an animation. Honour the preference now so
   they do not have to remember to. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
