:root {
  --ground: #f6f8fa;
  --surface: #ffffff;
  --surface-alt: #eef2f5;
  --ink: #15181d;
  --ink-mid: #414b58;
  --ink-mute: #5e6975;
  --rule: #dce2e8;
  --rule-firm: #c3ccd6;
  --accent: #0e6e6c;
  --warn: #9a5b0b;
  --warn-soft: #f7eddd;
  --pi: #0e6e6c;
  --copi: #5e6975;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --sans: system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Form controls share one geometry so that inputs, selects and buttons sitting
     on the same row line up exactly. Height is set explicitly rather than left to
     padding, because a <select> and an <input> compute their intrinsic height
     differently and drift apart by a pixel or two otherwise. */
  /* ---------------------------------------------------------- spacing scale
     One ladder, 4px-based, for every padding, margin and gap in the app. It
     replaced 25 distinct ad-hoc values across 167 declarations -- effectively
     every integer from 1 to 26 -- which is how two things meant to line up end
     up a pixel or two apart with nothing in the code admitting it.
     Anything not on this ladder is a bug, not a preference. */
  --sp-05: 2px;
  --sp-1:  4px;
  --sp-15: 6px;
  --sp-2:  8px;
  --sp-3:  12px;
  --sp-4:  16px;
  --sp-5:  20px;
  --sp-6:  24px;
  --sp-8:  32px;
  --sp-10: 40px;
  --sp-20: 80px;

  --ctl-h: 34px;
  /* Table rows need a smaller control, but one smaller size -- not a different
     one per widget. Everything in the accounts table uses this. */
  --ctl-h-sm: 27px;
  --ctl-pad-x: 10px;
  --ctl-font: 14px;
  --ctl-radius: 4px;
  --ctl-caret: 28px;
}

/* Three theme states, not two: light, dark, and "follow the system", which is the
   default and the one most people never change. That needs the dark palette in two
   places -- once for the system preference and once for an explicit choice -- and
   CSS has no way to share a declaration block between a media query and a plain
   selector, so the duplication below is deliberate rather than an oversight.

   The system rule is guarded with :not([data-theme="light"]) so that choosing
   light on a machine set to dark actually wins; without the guard the media query
   would override the explicit choice and the toggle would appear broken in one
   direction only. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground: #0e1114;
    --surface: #161a1f;
    --surface-alt: #1d232a;
    --ink: #e5e9ed;
    --ink-mid: #b3bdc8;
    --ink-mute: #8d98a5;
    --rule: #29313a;
    --rule-firm: #3a444f;
    --accent: #45b5ac;
    --warn: #d3a057;
    --warn-soft: #322717;
    --pi: #45b5ac;
    --copi: #8d98a5;
  }
}

:root[data-theme="dark"] {
  --ground: #0e1114;
  --surface: #161a1f;
  --surface-alt: #1d232a;
  --ink: #e5e9ed;
  --ink-mid: #b3bdc8;
  --ink-mute: #8d98a5;
  --rule: #29313a;
  --rule-firm: #3a444f;
  --accent: #45b5ac;
  --warn: #d3a057;
  --warn-soft: #322717;
  --pi: #45b5ac;
  --copi: #8d98a5;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.55;
}

a { color: var(--accent); }
code { font-family: var(--mono); font-size: 0.9em; background: var(--surface-alt); padding: var(--sp-05) var(--sp-1); border-radius: 3px; }
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.nowrap { white-space: nowrap; }
.r { text-align: right; }
.dim { color: var(--ink-mute); }
.empty { color: var(--ink-mute); font-style: italic; }

/* ---------------- topbar ---------------- */
/* Three zones, not a flex row: the search box has to sit in the CENTRE of the bar
   regardless of how wide the brand or the account cluster happen to be. A flex
   row with margin-left:auto centres nothing -- it only pushes things apart, so
   the search would drift left or right as the email length changed. A grid whose
   middle column takes the remaining space keeps it centred against the bar, not
   against its neighbours. */
.topbar {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-5);
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
  position: sticky; top: 0; z-index: 20;
}
/* The positioning context for the browse panel below. Anchoring the panel to
   the whole left zone rather than to the button aligns its left edge with the
   brand -- and so with the page's content margin -- instead of leaving it
   indented under the hamburger with nothing to line up against. */
.topleft {
  position: relative;
  display: flex; align-items: center; gap: var(--sp-3); min-width: 0;
}
.topbar .search { justify-self: center; width: 100%; max-width: 460px; margin: 0; }
.topbar .search input { width: 100%; }

/* -------------------------------------------------------------- browse menu */
.menu { position: static; }   /* panel anchors to .topleft, not to the button */
.menu > summary { list-style: none; }
.menu > summary::-webkit-details-marker { display: none; }
.menu[open] > summary { border-color: var(--accent); color: var(--accent); }
/* Closed shows the menu glyph, open shows a close mark -- state without script. */
.menu > summary .i-close { display: none; }
.menu[open] > summary .i-menu { display: none; }
.menu[open] > summary .i-close { display: block; }
.menupanel {
  position: absolute;
  align-items: stretch; gap: 0; top: calc(100% + 6px); left: 0; z-index: 30;
  display: flex; flex-direction: column; min-width: 210px;
  background: var(--surface); border: 1px solid var(--rule-firm);
  border-radius: 5px; padding: var(--sp-15);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .12);
}
.menupanel a {
  padding: var(--sp-2) var(--sp-3); border-radius: 3px; text-decoration: none;
  color: var(--ink-mid); font-size: 13.5px; white-space: nowrap;
}
.menupanel a:hover { background: var(--surface-alt); color: var(--accent); }
.brand {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: 17px; text-decoration: none; letter-spacing: -0.02em;
  color: var(--ink);
}
.brandmark { width: 21px; height: 21px; color: var(--accent); flex: none; }
.wordmark { font-weight: 400; }
.wordmark b { font-weight: 700; }
.brand:hover .brandmark { color: var(--ink); }
/* The horizontal `.topbar nav` rules that used to live here are gone. Once the
   sections moved into the dropdown, .menupanel became the only <nav> in the
   header, so a rule written for a row of links was silently restyling a column:
   its align-items:center is the HORIZONTAL axis in a column flex, so it centred
   every link, and its gap:16px overrode the panel's own gap:0. .menupanel and
   .menupanel a already define the whole component -- this was pure leftover. */
.topright {
  display: flex; align-items: center; gap: var(--sp-2); justify-content: flex-end;
  min-width: 0;
}
.topright .who {
  display: flex; align-items: center; gap: var(--sp-15);
  text-decoration: none; color: var(--ink-mid); font-size: 13px;
  max-width: 220px;
}
.topright a.who:hover { color: var(--accent); }
/* The address can be long, so it truncates rather than pushing the sign-out
   button off the bar -- the role beside it must stay visible. */
.topright .name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.topright .role {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-mute); border: 1px solid var(--rule-firm);
  border-radius: 3px; padding: 0 var(--sp-15); flex: none;
}

@media (max-width: 720px) {
  /* Below this the bar is already wrapping; the email is the first thing that can
     go, because the role badge and Sign out are what you act on. */
  .topright .name { display: none; }
}

@media (max-width: 900px) {
  /* Below this the three zones cannot all fit on one line, so the bar becomes two
     rows with the search spanning the full width underneath -- still centred,
     still the widest thing in the bar. */
  .topbar { grid-template-columns: auto auto; row-gap: var(--sp-2); }
  .topbar .search { grid-column: 1 / -1; grid-row: 2; max-width: none; }
  .topright { justify-self: end; }
}
.topbar input { width: min(340px, 46vw); }

main { max-width: 1180px; margin: 0 auto; padding: var(--sp-6) var(--sp-6) var(--sp-20); }

h1 { font-size: 26px; letter-spacing: -0.02em; margin: 0 0 var(--sp-5); line-height: 1.2; }
h2 { font-size: 17px; letter-spacing: -0.01em; margin: var(--sp-8) 0 var(--sp-3); }
.crumb { color: var(--ink-mute); font-size: 13px; margin: 0 0 var(--sp-15); }

/* ---------------- stat row ---------------- */
.stats {
  display: grid; align-items: stretch;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: var(--sp-05); background: var(--rule); border: 1px solid var(--rule); margin-bottom: var(--sp-5);
}
.stat { background: var(--surface); padding: var(--sp-3) var(--sp-4); }
.stat .v {
  display: block; font-size: 21px; font-weight: 600; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; line-height: 1.15;
}
.stat .l { display: block; font-size: 12px; color: var(--ink-mute); margin-top: var(--sp-1); }

/* ---------------- notes ---------------- */
.note {
  font-size: 13.5px; color: var(--ink-mute); background: var(--surface);
  border-left: 3px solid var(--rule-firm); padding: var(--sp-2) var(--sp-3); margin: 0 0 var(--sp-5);
}
.note.warn { border-left-color: var(--warn); background: var(--warn-soft); color: var(--ink-mid); }

/* ---------------- tables ---------------- */
.tablewrap { overflow-x: auto; border: 1px solid var(--rule); background: var(--surface); margin-bottom: var(--sp-6); }
table { border-collapse: collapse; width: 100%; font-size: 13.5px; }
table.narrow { width: auto; min-width: 280px; }
th {
  text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--ink-mute); padding: var(--sp-2) var(--sp-3); background: var(--surface-alt);
  border-bottom: 1px solid var(--rule-firm); white-space: nowrap;
}
td { padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--rule); color: var(--ink-mid); vertical-align: top; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover td { background: var(--surface-alt); }
td.title { color: var(--ink); max-width: 460px; }
tr.soon td { border-left: 3px solid var(--warn); }

/* ---------------- pills and chips ---------------- */
/* nowrap because the hyphen in "co-PI" is a break opportunity, and the Role
   column is the narrowest in the table: the label broke after the hyphen and
   rendered as a two-line box twice the height of the PI pill beside it. A label
   inside a badge should never wrap -- the badge is sized by its text, not the
   other way round. */
.pill {
  display: inline-block; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em;
  padding: var(--sp-05) var(--sp-15); border: 1px solid currentColor; border-radius: 3px;
  vertical-align: middle; white-space: nowrap;
}
.pill.pi { color: var(--pi); }
.pill.copi { color: var(--copi); }
.pill.former { color: var(--warn); }
.pill.soft { color: var(--ink-mute); border-style: dashed; }

.chips { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2);
         margin: 0 0 var(--sp-5); }
.chip {
  display: inline-block; font-size: 12.5px; padding: var(--sp-1) var(--sp-2);
  background: var(--surface); border: 1px solid var(--rule-firm); border-radius: 3px;
  text-decoration: none; color: var(--ink-mid);
}
.chip .ct, .yr .ct { color: var(--ink-mute); font-size: 11px; margin-left: var(--sp-1); }

.years { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-15);
         margin: 0 0 var(--sp-4); }
.yr {
  font-size: 12.5px; padding: var(--sp-1) var(--sp-2); text-decoration: none; color: var(--ink-mid);
  border: 1px solid var(--rule-firm); border-radius: 3px; background: var(--surface);
  font-family: var(--mono);
}
.yr.on { background: var(--accent); border-color: var(--accent); color: #fff; }

/* ---------------- definition lists ---------------- */
.facts {
  display: grid; align-items: baseline;
  grid-template-columns: max-content 1fr; gap: var(--sp-2) var(--sp-5);
  margin: 0 0 var(--sp-6); font-size: 14px;
}
.facts dt { color: var(--ink-mute); font-size: 12.5px; }
.facts dd { margin: 0; color: var(--ink-mid); }

/* ---------------- lists ---------------- */
.people, .deadlines { list-style: none; padding: 0; margin: 0 0 var(--sp-5); }
.people li, .deadlines li {
  padding: var(--sp-2) 0; border-bottom: 1px solid var(--rule);
  display: flex; gap: var(--sp-2); align-items: baseline; flex-wrap: wrap;
}
.deadlines .date { color: var(--ink-mute); font-size: 12.5px; min-width: 88px; }

/* ---------------- bar chart ---------------- */
.bars { display: flex; flex-direction: column; align-items: stretch;
        gap: var(--sp-1); margin-bottom: var(--sp-6); }
.bar { display: grid; grid-template-columns: 62px 1fr 108px 46px; gap: var(--sp-3); align-items: center; font-size: 12.5px; }
.bar .bl { color: var(--ink-mute); }
.bar .bt { background: var(--surface-alt); height: 17px; border: 1px solid var(--rule); }
.bar .bt i { display: block; height: 100%; background: var(--accent); }
.bar .bv { text-align: right; color: var(--ink-mid); }
.bar .bn { text-align: right; color: var(--ink-mute); }

/* ---------------- abstract ---------------- */
.abstract {
  background: var(--surface); border: 1px solid var(--rule); padding: var(--sp-4) var(--sp-5);
  max-width: 76ch; white-space: pre-wrap; color: var(--ink-mid); font-size: 14.5px; line-height: 1.62;
}

.cols { display: grid; align-items: start; grid-template-columns: 1fr; gap: var(--sp-6); }

footer {
  border-top: 1px solid var(--rule); padding: var(--sp-4) var(--sp-6);
  color: var(--ink-mute); font-size: 12px; text-align: center;
}

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------------- ego network (Phase 2) ---------------- */
.egocontrols {
  display: flex; flex-wrap: wrap; gap: var(--sp-4) var(--sp-6); align-items: center;
  background: var(--surface); border: 1px solid var(--rule);
  padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-3); font-size: 12.5px; color: var(--ink-mute);
}
.egocontrols label { display: flex; align-items: center; gap: var(--sp-2); }
.egocontrols input[type="range"] { width: 118px; accent-color: var(--accent); }

.egocontrols .mono { color: var(--ink); min-width: 46px; }
#egograph {
  height: 460px; background: var(--surface);
  border: 1px solid var(--rule); margin-bottom: var(--sp-6);
}

/* ---------------- recommender (Phase 3) ---------------- */
.recform { background: var(--surface); border: 1px solid var(--rule); padding: var(--sp-4); margin-bottom: var(--sp-5); }
.recform textarea { width: 100%; resize: vertical; }
.recrow { display: flex; flex-wrap: wrap; gap: var(--sp-4); align-items: center; margin-top: var(--sp-3); font-size: 13px; color: var(--ink-mute); }
.recrow input[type="number"] { width: 108px; margin-left: var(--sp-15); }
.recrow .chk { display: flex; align-items: center; gap: var(--sp-15); }
.recrow button {
  font: inherit; font-size: 13px; font-weight: 600; padding: var(--sp-15) var(--sp-5); margin-left: auto;
  background: var(--accent); color: #fff; border: none; border-radius: 5px; cursor: pointer;
}
.weights { margin-top: var(--sp-3); font-size: 13px; }
.weights summary { color: var(--ink-mute); cursor: pointer; }
.wgrid { align-items: start; display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--sp-3) var(--sp-6); margin-top: var(--sp-3); }
.wgrid label { display: grid; grid-template-columns: 1fr 110px 34px; align-items: center; gap: var(--sp-2); }
.wgrid .wn { font-size: 12.5px; color: var(--ink-mid); }
.wgrid .wv { font-size: 12px; color: var(--ink); text-align: right; }
/* Was margin-top:-4px, cancelling the grid's own row gap. Pulling an element
   back out of the space its container just added is a patch over the wrong gap;
   the row simply has no top margin. */
.wgrid .wh { grid-column: 1 / -1; font-size: 11.5px; color: var(--ink-mute); margin-top: 0; }
.wgrid input[type="range"] { width: 100%; accent-color: var(--accent); }

/* ---------------------------------------------- trends and topics (Phase 3) */
/* Bars are CSS widths rather than SVG: the chart is one quantity per row, so a
   drawn axis would add furniture without adding information, and this keeps the
   page working with no script and no layout pass. */
.barchart { margin: var(--sp-4) 0 var(--sp-6); }
.barrow {
  display: grid;
  grid-template-columns: 54px 1fr 128px 64px;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-1) 0;
}
.barlabel { color: var(--ink-mid); font-size: 13px; }
.bartrack { background: var(--surface-alt); height: 15px; border-radius: 2px; }
/* Scoped to .bartrack on purpose. An unscoped `.bar` here silently overrode the
   grid `.bar` the program page has used since Phase 1 -- same name, later in the
   file, so it won -- and collapsed those rows into solid blocks. */
.bartrack .bar { display: block; height: 15px; background: var(--accent); border-radius: 2px; }
.barval { text-align: right; font-size: 13px; }
.barn { text-align: right; font-size: 12px; }

.warnbox {
  background: var(--warn-soft);
  border-left: 3px solid var(--warn);
  padding: var(--sp-3) var(--sp-3);
  margin: var(--sp-4) 0;
  font-size: 14px;
  color: var(--ink-mid);
}
.crumb a.on { color: var(--accent); font-weight: 600; }

@media (max-width: 640px) {
  .barrow { grid-template-columns: 44px 1fr 96px; }
  .barrow .barn { display: none; }
}

/* ------------------------------------------------- accounts and settings UI */
.narrow { max-width: 460px; }
.stack { display: flex; flex-direction: column; align-items: stretch;
         gap: var(--sp-3); margin: var(--sp-4) 0; }
.stack label { display: flex; flex-direction: column; align-items: stretch;
               gap: var(--sp-1); font-size: 14px; color: var(--ink-mid); }

.linkish { background: none; color: var(--accent); padding: var(--sp-05) var(--sp-1);
           text-decoration: underline; font-size: 13px; }
/* The one colour outside the palette, so it needs the same three-state handling
   the variables get -- otherwise an explicit theme choice would move every colour
   on the page except this one. */
:root { --danger: #b3261e; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --danger: #f2837b; }
}
:root[data-theme="dark"] { --danger: #f2837b; }
.linkish.danger { color: var(--danger); }
.inline { display: inline; }
/* The logout form is a wrapper, not a control. As display:inline it contributed
   its own inline box to the header row, so the gap either side of the sign-out
   icon did not match the gap around the theme icon. As a flex box sized to the
   button, one --sp-2 separates every item in the cluster. */
.topright .inline { display: flex; align-items: center; }
.rowactions { white-space: nowrap; }
.subrow td { padding-top: 0; border-top: 0; }
.subrow input[type=password] { width: 300px; max-width: 60%; }

.setting label { display: block; font-size: 14px; color: var(--ink-mid);
                 margin-bottom: var(--sp-1); }
.setting input[type=text], .setting input[type=password], .setting input[type=number] {
  width: 100%; max-width: 460px;
}
.settinghelp { font-size: 12.5px; color: var(--ink-mute); margin: var(--sp-15) 0 0; }
.src { font-family: var(--mono); font-size: 11px; background: var(--surface-alt);
       border-radius: 2px; padding: var(--sp-05) var(--sp-15); margin-left: var(--sp-15); }
.clearbox { display: inline; margin-left: var(--sp-2); font-size: 12px; }

.okbox, .errorbox {
  padding: var(--sp-3) var(--sp-3); margin: var(--sp-4) 0; font-size: 14px; border-left: 3px solid;
}
.okbox { background: var(--surface-alt); border-color: var(--accent); color: var(--ink-mid); }
.errorbox { background: var(--warn-soft); border-color: #b3261e; color: var(--ink); }
footer { display: flex; gap: var(--sp-3); align-items: baseline; justify-content: center; }

/* -------------------------------------------------------- refresh / run bar */
.runbar {
  display: flex; align-items: center; gap: var(--sp-4);
  margin: var(--sp-4) 0; flex-wrap: wrap;
}
.runbar .runinfo { flex: 1 1 320px; min-width: 0; }
.runbar .runinfo > p { margin: 0; }
.runbar form { flex: none; }
.runbar button[disabled] { opacity: .55; cursor: default; filter: none; }

/* Rotation only, no colour change, so it reads as "working" without competing
   with the accent used for links and bars. */
.spinner {
  display: inline-block; width: 11px; height: 11px; margin-right: var(--sp-15);
  vertical-align: -1px; border-radius: 50%;
  border: 2px solid var(--rule-firm); border-top-color: var(--accent);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Respect a user who has asked for less motion: the text already says what is
   happening, so the spinner can simply stop. */
@media (prefers-reduced-motion: reduce) { .spinner { animation: none; } }

/* ------------------------------------------------- filters and pagination */
.filters {
  display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; margin: var(--sp-4) 0;
}
.filters input[type=search] { min-width: 260px; flex: 1 1 260px; max-width: 420px; }
.filters .clearf { font-size: 13px; color: var(--ink-mute); }

.pager {
  display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap;
  margin: var(--sp-3) 0; font-size: 13px; color: var(--ink-mute);
}
/* The count sits on the left of every list, because a page that shows 50 of
   1,737 and says nothing reads as "these are all of them". */
.pagecount { flex: 1 1 auto; }
@media (max-width: 640px) { .pager { gap: var(--sp-2); } }

/* ------------------------------------------------- control geometry (shared) */
/* Every text input, select and button in the app resolves to the same height,
   font, border and background, so a filter bar, a settings row and a pager read
   as one system rather than three.

   Matched on the ELEMENT, not on a list of places. The previous version named
   .filters, .stack, .setting, .subrow and .topbar, which meant every new context
   started life unstyled and silently wrong: .resultbar's save field and
   .rowform's "add an account" fields both rendered at the browser's ~21px
   default beside 34px buttons, and appearance had drifted to three border radii
   (3/4/5px), two backgrounds and three font sizes across the app. An element
   selector cannot be forgotten when a new form is added.

   Checkboxes, radios and ranges opt out -- they are not text boxes, and a 34px
   box is the wrong shape for them. A textarea takes the appearance but keeps an
   auto height, because it is the one control that is meant to grow. */
input:not([type=checkbox]):not([type=radio]):not([type=range]),
select, textarea, button, .btn {
  font: inherit;
  font-size: var(--ctl-font);
  border-radius: var(--ctl-radius);
  vertical-align: middle;
}
input:not([type=checkbox]):not([type=radio]):not([type=range]),
select, textarea {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--rule-firm);
}
input:not([type=checkbox]):not([type=radio]):not([type=range]),
select, button, .btn {
  height: var(--ctl-h);
  padding: 0 var(--ctl-pad-x);
  line-height: calc(var(--ctl-h) - 2px);
}
textarea { padding: var(--sp-2) var(--ctl-pad-x); line-height: 1.5; }
input:not([type=checkbox]):not([type=radio]):not([type=range]):focus,
select:focus, textarea:focus {
  outline: 2px solid var(--accent); outline-offset: -1px;
}
/* Buttons carry a little more horizontal padding: a label needs more breathing
   room than a caret or a cursor does. */
button, .btn {
  padding: 0 var(--sp-4); border: 1px solid transparent; cursor: pointer;
  background: var(--accent); color: #fff; text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--sp-15);
}
button:hover, .btn:hover { filter: brightness(1.08); }
/* A secondary action next to a primary one. Two filled buttons side by side read
   as two equally-weighted choices, which is not what "Save" and "Export" are. */
button.secondary, .btn.secondary {
  background: none; color: var(--accent); border-color: var(--rule-firm);
}
button.secondary:hover, .btn.secondary:hover {
  border-color: var(--accent); filter: none; background: var(--surface-alt);
}
/* Clearance for the native select caret. A token rather than a loose number:
   it is a property of the control, not a spacing choice, and it must match
   wherever a select appears. */
select { padding-right: var(--ctl-caret); }

/* .linkish is a button that must read as a link, so it opts out of the geometry
   above entirely rather than inheriting a 34px box inside a table row. */
.linkish {
  height: auto; padding: var(--sp-05) var(--sp-1); line-height: inherit;
  font-size: 13px; border: 0; border-radius: 0;
}

.filters, .stack, .pager, .runbar { align-items: center; }
.stack { align-items: stretch; }
.stack label { gap: var(--sp-15); }

/* Tabular figures everywhere a number sits in a column, so digits align down the
   page instead of jittering by glyph width. */
.stat .v, .pagecount, td.r, th.r { font-variant-numeric: tabular-nums; }

/* Breadcrumb pager. The trail is an <ol> so it is a list to a screen reader, and
   the separators are CSS content so they are never read aloud as text. */
.crumbs {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0;
  list-style: none; margin: 0; padding: 0;
}
.crumbs li { display: flex; align-items: center; gap: 0; }
.crumbs li + li::before {
  content: "›"; color: var(--rule-firm); padding: 0 var(--sp-15); font-size: 12px;
}
.crumbs a, .crumbs .on, .crumbs .off {
  display: inline-flex; align-items: center; justify-content: center; gap: 0;
  min-width: 26px; height: 26px; padding: 0 var(--sp-2);
  border-radius: var(--ctl-radius); text-decoration: none;
  font-variant-numeric: tabular-nums; font-size: 13px;
}
.crumbs a { color: var(--accent); }
.crumbs a:hover { background: var(--surface-alt); }
.crumbs .on { background: var(--accent); color: #fff; font-weight: 600; }
.crumbs .off { color: var(--ink-mute); opacity: .45; }
.crumbs .gap { color: var(--ink-mute); padding: 0 var(--sp-05); }
.crumbs .gap::before { content: none; }

/* ------------------------------------------------------ charts and word cloud */
/* Everything is inline SVG themed through the same variables as the rest of the
   site, so light/dark works without a second palette. */
.chart { width: 100%; height: auto; display: block; margin: var(--sp-15) 0 var(--sp-5); }
.chart .area  { fill: var(--accent); opacity: .16; }
.chart .line  { fill: none; stroke: var(--accent); stroke-width: 2;
                stroke-linejoin: round; }
.chart .dot   { fill: var(--accent); }
.chart .grid  { stroke: var(--rule); stroke-width: 1; }
.chart .ytick, .chart .xtick {
  fill: var(--ink-mute); font-size: 10.5px; font-family: var(--mono);
}

/* Eight bands with distinct lightness as well as hue, so the stack stays readable
   in greyscale and for the ~8% of men with red-green colour deficiency. */
/* .stackchart, not .stack: .stack is the vertical FORM layout used by login,
   signup, setup, users and the password form, and the stacked chart was reusing
   the same name for an <svg>. The chart was inheriting display:flex,
   flex-direction:column, gap:12px and margin from a rule written for form
   fields. Two unrelated components, one class name -- the same mistake that put
   the horizontal nav rules onto the dropdown panel. */
.stackchart .s0 { fill: #0e6e6c; } .stackchart .s1 { fill: #7a4fa3; }
.stackchart .s2 { fill: #b4763a; } .stackchart .s3 { fill: #2f6fb0; }
.stackchart .s4 { fill: #97335c; } .stackchart .s5 { fill: #4c7a2b; }
.stackchart .s6 { fill: #8a6d1f; } .stackchart .s7 { fill: #5e6975; }
.legend { align-items: center; display: flex; flex-wrap: wrap; gap: var(--sp-15) var(--sp-4); font-size: 12px;
          color: var(--ink-mute); margin: 0 0 var(--sp-5); }
.legend .key::before {
  content: ""; display: inline-block; width: 9px; height: 9px; border-radius: 2px;
  margin-right: var(--sp-15); vertical-align: baseline; background: currentColor;
}
.legend .s0 { color: #0e6e6c; } .legend .s1 { color: #7a4fa3; }
.legend .s2 { color: #b4763a; } .legend .s3 { color: #2f6fb0; }
.legend .s4 { color: #97335c; } .legend .s5 { color: #4c7a2b; }
.legend .s6 { color: #8a6d1f; } .legend .s7 { color: #5e6975; }

.cloud { width: 100%; height: auto; display: block;
         background: var(--surface); border: 1px solid var(--rule); margin: var(--sp-15) 0 var(--sp-3); }
.cloud .w { font-family: var(--sans); font-weight: 600; dominant-baseline: alphabetic; }
/* Rank bands fade toward the muted end rather than changing hue, so colour is
   not read as a second variable. */
.cloud .b0 { fill: var(--accent); }
.cloud .b1 { fill: var(--ink); }
.cloud .b2 { fill: var(--ink-mid); }
.cloud .b3 { fill: var(--ink-mute); }
.cloud .b4 { fill: var(--ink-mute); opacity: .72; }

.termtable summary { cursor: pointer; font-size: 13.5px; color: var(--accent);
                     margin-bottom: var(--sp-3); }
td.up   { color: var(--accent); }
td.down { color: var(--warn); }
.inlinecheck { display: inline-flex; align-items: center; gap: var(--sp-15);
               font-size: 13.5px; color: var(--ink-mid); }
.inlinecheck input { height: auto; }

/* --------------------------------------------------- division drill-down list */
.divisions { margin: var(--sp-4) 0 var(--sp-5); }
.divisions summary { cursor: pointer; font-size: 13.5px; color: var(--accent);
                     margin-bottom: var(--sp-2); }
.divisions .dirhead {
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--ink-mute); margin: var(--sp-3) 0 var(--sp-15); font-weight: 600;
}
.divisions .chips { gap: var(--sp-15); margin-bottom: var(--sp-1); }
.divisions .chip { font-family: var(--mono); font-size: 12px; padding: var(--sp-05) var(--sp-2); }
.divisions .chip.on { background: var(--accent); border-color: var(--accent); color: #fff; }
.divisions .chip.on .ct { color: rgba(255,255,255,.8); }

/* --------------------------------------------------------- theme toggle */
/* One icon-button, used at both ends of the bar. Defining it once is what keeps
   the menu and the theme control the same size -- they sat at 32px and 34px when
   each carried its own geometry, which is visible as a step in a single row. */
.iconbtn {
  gap: 0;
  background: none; border: 1px solid var(--rule-firm); color: var(--ink-mid);
  width: 32px; height: 32px; min-height: 0; padding: 0;
  border-radius: var(--ctl-radius);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; flex: none; line-height: 1;
}
.iconbtn:hover { color: var(--accent); border-color: var(--accent); }
/* Icon buttons in the header carry no outline. Three bordered boxes in a row read
   as a control cluster competing with the search field; the icons alone are
   quieter and the row is easier to scan. border-color rather than border-width so
   the box keeps exactly the geometry every other control in the bar has, and
   hover falls back to a wash since there is no longer an edge to tint. */
.topbar .iconbtn { border-color: transparent; }
.topbar .iconbtn:hover { border-color: transparent; background: var(--surface-alt); }
.iconbtn svg { width: 16px; height: 16px; display: block; }
/* Exactly one icon is shown, chosen by the state on <html>. Doing this in CSS
   rather than by rewriting the DOM means the correct icon is present in the very
   first paint, with no flicker while a script decides. */
/* The icon names the destination, not the current state: moon while light, sun
   while dark. Selected in CSS rather than by a script rewriting the DOM, so the
   right glyph is in the first paint instead of appearing a frame later.
   Both the explicit attribute and the untouched system default are covered --
   otherwise the button would be blank until first pressed. */
.themetoggle .i-sun { display: none; }
.themetoggle .i-moon { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .themetoggle .i-moon { display: none; }
  :root:not([data-theme]) .themetoggle .i-sun  { display: block; }
}
:root[data-theme="light"] .themetoggle .i-moon { display: block; }
:root[data-theme="light"] .themetoggle .i-sun  { display: none; }
:root[data-theme="dark"]  .themetoggle .i-moon { display: none; }
:root[data-theme="dark"]  .themetoggle .i-sun  { display: block; }

/* ------------------------------------------------------- settings, compacted */
/* Cards in a responsive grid: the five setting groups are short and unrelated, so
   stacking them full-width made the page four screens tall for about a screen of
   actual content. auto-fit lets two or three sit side by side on a wide window and
   fall to one column on a narrow one, with no breakpoint to maintain. */
.cards {
  display: grid; gap: var(--sp-4); margin: var(--sp-4) 0 var(--sp-2);
  grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
  align-items: start;
}
.card {
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: 5px; padding: var(--sp-4) var(--sp-4) var(--sp-4);
}
.card > h2 { margin: 0 0 var(--sp-1); font-size: 14px; letter-spacing: .01em; }
.card > h2:first-child { margin-top: 0; }
.cardnote { font-size: 12.5px; color: var(--ink-mute); margin: 0 0 var(--sp-3); line-height: 1.5; }

/* Label and control on one line with the help beneath, rather than three stacked
   blocks: it halves the height of every row and keeps the label next to the thing
   it labels. */
.card .setting {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-1) var(--sp-3);
  padding: var(--sp-2) 0;
  border-top: 1px solid var(--rule);
  align-items: center;
}
.card .setting:first-of-type { border-top: 0; padding-top: var(--sp-05); }
.card .setting > label {
  font-size: 13px; color: var(--ink); margin: 0; grid-column: 1;
  line-height: 1.35;
}

/* Control and badge in one cell, so an absent badge costs no height. */
.card .setting .control {
  grid-column: 2; display: flex; align-items: center; gap: var(--sp-2);
  justify-content: flex-end; min-width: 0;
}
/* Widths by what the field holds rather than one size for all: a rescan window of
   "45" in a 190px box looks like a mistake, and an endpoint URL in the same box is
   unreadable. */
.card .setting input.w-wide { width: 200px; max-width: 44vw; }
.card .setting input.w-num  { width: 92px; text-align: right; }
.card .setting select { width: auto; min-width: 108px; }

/* The checkbox opts out of the shared 34px control geometry -- at that height it
   renders as a large empty square that reads as a broken text field. */
.card .setting input[type=checkbox] {
  width: 16px; height: 16px; min-height: 0; padding: 0;
  accent-color: var(--accent); cursor: pointer;
}
.card .setting.is-bool > label { cursor: pointer; }
.card .setting .meta { display: inline-flex; align-items: center; gap: var(--sp-15); }
.card .setting .clearbox { margin: 0; white-space: nowrap; }
.card .setting .clearbox input[type=checkbox] { width: 13px; height: 13px; }

@media (max-width: 460px) {
  /* Stack rather than squeeze: below this the two columns leave the control a
     sliver, and a URL field you cannot read is worse than a taller page. */
  .card .setting { grid-template-columns: 1fr; }
  .card .setting .control { grid-column: 1; justify-content: flex-start; }
  .card .setting input.w-wide { width: 100%; max-width: none; }
}
.card .setting .settinghelp {
  grid-column: 1 / -1; margin: var(--sp-05) 0 0; font-size: 11.5px; line-height: 1.45;
  color: var(--ink-mute);
}
.formbar { margin: var(--sp-1) 0 var(--sp-6); }


table.compact th, table.compact td { padding: var(--sp-15) var(--sp-3); font-size: 13px; }
tr.inactive td { opacity: .6; }
/* One geometry for every small control -- input, select and button alike -- so a
   table row lines up instead of stepping. Replaces a .mini rule that set only
   height and width, with !important, and left buttons at the full 34px. */
.sm, .btn.sm, button.sm {
  height: var(--ctl-h-sm); line-height: calc(var(--ctl-h-sm) - 2px);
  font-size: 12.5px; padding: 0 var(--sp-3); border-radius: var(--ctl-radius);
}
input.sm { width: 168px; }

/* ------------------------------------------------------ top bar control sizing */
/* The bar had four heights in it at once -- a 34px search field, 32px icon
   buttons, 27px .sm buttons and a 20px account link -- so nothing lined up with
   anything and the row read as four separate widgets. Everything interactive in
   the header is now one height, the same --ctl-h the filter bars use, so the
   header and the page below it are visibly the same system.
   This sits after the .sm rules on purpose: `.topright .btn` and `.btn.sm` have
   equal specificity, so only source order decides which wins. */
.topbar .search input,
.topright .btn,
.topbar .iconbtn {
  height: var(--ctl-h);
}
.topbar .iconbtn { width: var(--ctl-h); }
.topright .btn {
  font-size: var(--ctl-font);
  line-height: calc(var(--ctl-h) - 2px);
  padding: 0 var(--sp-3);
}
/* The account link is not a control, but it shares the row, so it gets the same
   box height -- otherwise it floats off-centre against everything beside it. */
.topright .who { height: var(--ctl-h); }
select.sm { width: auto; padding-right: var(--sp-6); }

/* A disclosure inside a table cell: keeps the reset field out of the layout until
   it is asked for, instead of a permanent second row per user. */
/* The disclosure's summary IS the button -- styled with .btn.sm.secondary in the
   markup - so it matches Disable and Delete beside it rather than being an
   underlined link among buttons. list-style:none removes the default triangle,
   which would otherwise sit inside the button box. */
.pwreset { display: inline-block; }
.pwreset > summary {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-1);
  cursor: pointer; list-style: none;
}
.pwreset > summary::-webkit-details-marker { display: none; }
.pwreset[open] > summary { margin-bottom: var(--sp-15); }
.pwform { display: flex; align-items: center; gap: var(--sp-15); }

/* Actions wrap as a unit instead of drifting apart in a narrow column. */
.rowactions { display: flex; align-items: center; gap: var(--sp-15); flex-wrap: wrap; }
td .rowactions { white-space: normal; }
.btn.sm.danger, button.sm.danger {
  background: none; color: var(--danger); border-color: var(--rule-firm);
}
.btn.sm.danger:hover, button.sm.danger:hover {
  border-color: var(--danger); filter: none; background: var(--surface-alt);
}
/* Middle-aligned so a 27px control sits centred against the text beside it. */
table.compact td { vertical-align: middle; }

.addbox { margin-top: var(--sp-3); font-size: 13px; }
.addbox summary { cursor: pointer; color: var(--accent); }
.rowform { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center;
           margin-top: var(--sp-3); }
.rowform input[type=email] { min-width: 210px; }

.historybox { margin-top: var(--sp-4); }
.historybox summary { cursor: pointer; font-size: 13.5px; }
.historybox .tablewrap { margin-top: var(--sp-3); margin-bottom: 0; }

/* --------------------------------------------- search export / save controls */
/* Same geometry and spacing as .filters, so the row under a result set looks
   like the row above it rather than a separate widget. */
.resultbar {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  margin: 0 0 var(--sp-4);
}
.resultbar input[type=text] { width: 220px; }
.resultbar .rblabel { color: var(--ink-mute); font-size: 13px; }
.resultbar .sep { color: var(--ink-mute); font-size: 13px; padding: 0 var(--sp-05); }
.resultbar .hint { color: var(--ink-mute); font-size: 13px; }
td .up { color: var(--accent); font-size: 11px; margin-left: var(--sp-1); }

/* A card's own action, below its settings rows. */
.card .cardactions {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--rule);
}
.card .cardactions .settinghelp { margin: 0; flex: 1 1 160px; }

/* ------------------------------------------------------------ account requests */
/* The count an admin sees beside their name while people are waiting. --surface on
   --warn rather than white: in the dark theme --warn is a light amber, and white
   text on it would be unreadable. */
.reqbadge {
  display: inline-flex; align-items: center; justify-content: center; gap: 0;
  min-width: 18px; height: 18px; padding: 0 var(--sp-1); border-radius: 9px;
  background: var(--warn); color: var(--surface);
  font-size: 11px; font-weight: 600; line-height: 1; flex: none;
}
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  padding: 0; border: 0;
}
.card.requests {
  margin: var(--sp-4) 0; border-left: 3px solid var(--warn);
  scroll-margin-top: var(--sp-20);   /* clear the sticky header when linked to */
}
.requestlist {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; align-items: stretch; gap: 0;
}
.request {
  display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-2) var(--sp-4); padding: var(--sp-3) 0; border-top: 1px solid var(--rule);
}
.request:first-child { border-top: 0; padding-top: var(--sp-1); }
.request .requester {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-1) var(--sp-2);
  flex: 1 1 280px; min-width: 0;
}
.request .reqaddr { font-weight: 600; overflow-wrap: anywhere; }
/* pre-line keeps the line breaks the person typed; anywhere stops one long unbroken
   string from pushing the Approve button off the card. */
.request .requestnote {
  flex-basis: 100%; margin: 0; font-size: 13px; color: var(--ink-mid);
  white-space: pre-line; overflow-wrap: anywhere;
}
.request .rowactions { display: flex; align-items: center; gap: var(--sp-2); flex: none; }
