/*
  ARCOps stylesheet (TR-5): one file, no framework, semantic class names.

  How a page should read, and which class does what, is in docs/INTERFACE.md. Read it before
  adding a rule here. In short: one solid button per form, outline for a side action, link for
  a row action, .danger for anything destructive (underlined, never color alone), data-label on
  every cell so a table survives a phone.

  Color: --brand is the club's color, set inline by base.html from club.yaml (TR-40); the
  default below is the application's own. Everything else derives from it. In dark mode the
  brand stays as a background (footer, sign-in panel) and a lightened mix of it becomes the
  text and link color, so contrast holds in both schemes. Color never carries meaning on
  its own (FR-62): every status also has a word.

  Layout: signed in, a left sidebar (full, or an icon rail when the member collapses it; an
  off-canvas drawer under 60rem) beside the content, and the club-colored footer under both.
  Signed out, a split page: club panel and form. Without JavaScript the sidebar simply
  renders in full and the drawer controls are hidden (TR-4).
*/

:root {
  --brand: #1f3a5f;
  --brand-fg: #ffffff;
  --accent: var(--brand);
  --accent-fg: var(--brand-fg);
  --accent-soft: color-mix(in srgb, var(--brand) 9%, #ffffff);
  --accent-soft-fg: var(--brand);
  --bg: #ffffff; --fg: #1b1f24; --muted: #5a6470; --line: #d9dee5; --card: #f6f7f9; --side: #f3f4f7;
  --ok: #1b6e3a; --warn: #8a5a00; --bad: #9b1c1c; --bad-fg: #ffffff;
  /* The gold at #e0a100 was 2.3:1 on white, under the 3:1 a focus indicator needs.
     Darkened here, and every ring is drawn over a halo of the page color so it also
     holds against the brand-colored panels. */
  --focus: #9a6700; --focus-halo: #ffffff;
  --tag-ok: #d8f0e0; --tag-warn: #fff1cc; --tag-bad: #fbdada;
  --radius: .6rem;
  /* Four steps of space and five of type. Pick from these; a page that needs a sixth size
     usually needs a different structure instead. */
  --s1: .35rem; --s2: .75rem; --s3: 1.25rem; --s4: 2rem;
  --t-small: .85rem; --t-body: 1rem; --t-lede: 1.05rem; --t-h2: 1.25rem; --t-h1: 1.9rem;
  --sidebar-w: 15.5rem;
  --rail-w: 4.5rem;
  --shadow: 0 1px 2px rgb(0 0 0 / .06), 0 8px 24px rgb(0 0 0 / .06);
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #111418; --fg: #e8ecf1; --muted: #9aa5b1; --line: #2a323c; --card: #1a2028; --side: #151a20;
    --accent: color-mix(in srgb, var(--brand) 42%, #ffffff);
    --accent-fg: #0c1220;
    --accent-soft: color-mix(in srgb, var(--brand) 45%, #111418);
    --accent-soft-fg: var(--accent);
    --ok: #7ad19a; --warn: #f2c15a; --bad: #ff9b9b; --bad-fg: #3a0d0d;
    --focus: #f2c15a; --focus-halo: #111418;
    --tag-ok: #163d26; --tag-warn: #4a3a10; --tag-bad: #4a1a1a;
    --shadow: 0 1px 2px rgb(0 0 0 / .4), 0 8px 24px rgb(0 0 0 / .35);
  }
}

/* ------------------------------------------------------------------ base */
* { box-sizing: border-box; }
html { font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--fg); -webkit-text-size-adjust: 100%; }
/* svh, not dvh: the *small* viewport is the one that is always visible, whatever the phone's
   own bars are doing. In an installed app on Android, 100dvh can be the whole screen including
   the strip the navigation bar occupies, which pushed the footer's last line out of sight
   (NAF, 2026-09-19, twice). svh can only ever be smaller, so nothing is pinned to a height we
   cannot see. */
body { margin: 0; min-height: 100svh; display: flex; flex-direction: column; }
a { color: var(--accent); }
:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; box-shadow: 0 0 0 2px var(--focus-halo); }
h1 { font-size: 1.9rem; line-height: 1.2; letter-spacing: -.01em; margin: 0 0 1rem; }
h2 { font-size: 1.25rem; line-height: 1.3; margin: 1.5rem 0 .5rem; }
h1 + p, h2 + p { margin-top: 0; }
.muted { color: var(--muted); }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.skip { position: absolute; left: -999px; top: 0; background: var(--accent); color: var(--accent-fg); padding: .5rem 1rem; z-index: 50; }
.skip:focus { left: 0; }
.icon { width: 1.25rem; height: 1.25rem; flex: none; }

/* --------------------------------------------------------- app shell */
.shell { flex: 1; display: grid; grid-template-columns: var(--sidebar-w) minmax(0, 1fr); }
html[data-nav="rail"] .shell { grid-template-columns: var(--rail-w) minmax(0, 1fr); }
.shell > .banner, .shell > main { grid-column: 2; }
.shell > .banner { margin: 1rem clamp(1rem, 4vw, 3rem) 0; }

.topbar { display: none; }

.sidenav {
  position: sticky; top: 0; align-self: start; height: 100svh;
  display: flex; flex-direction: column; gap: .5rem;
  background: var(--side); border-right: 1px solid var(--line);
  padding: 1rem .75rem;
  overflow-y: auto;
}
.sidenav-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; padding: .25rem .25rem .75rem; }
.brand { display: flex; align-items: center; gap: .75rem; color: inherit; text-decoration: none; min-width: 0; }
.brand img { width: 3.5rem; height: 3.5rem; flex: none; }
.brand-text { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.brand-name { font-weight: 700; font-size: 1.15rem; letter-spacing: .01em; }
.brand-sub { color: var(--muted); font-size: .85rem; }

.navlist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .125rem; }
.navlist a, .navbtn {
  display: flex; align-items: center; gap: .75rem;
  padding: .55rem .75rem; border-radius: var(--radius);
  color: var(--fg); text-decoration: none; font: inherit; font-weight: 500;
  background: none; border: 0; width: 100%; text-align: left; cursor: pointer;
}
.navlist a .icon, .navbtn .icon, .whoami .icon { color: var(--muted); }
.navlist a:hover, .navbtn:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); }
.navlist a[aria-current="page"] { background: var(--accent-soft); color: var(--accent-soft-fg); font-weight: 600; }
.navlist a[aria-current="page"] .icon { color: currentColor; }
.navsep { height: 1px; background: var(--line); margin: .5rem .75rem; }
/* The sidebar is three groups: what any member uses, what an officer adds, what a sysadmin
   adds. Each later group is introduced by a rule and a small label, so the dividing line is
   visible; the label names the list for a screen reader through aria-labelledby. */
.navgroup {
  margin: .75rem .75rem .25rem; padding-top: .75rem; border-top: 1px solid var(--line);
  font-size: .7rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted);
}
.sidenav-foot { margin-top: auto; padding-top: .75rem; padding-bottom: env(safe-area-inset-bottom, 0px); border-top: 1px solid var(--line); display: flex; flex-direction: column; gap: .125rem; }
.whoami { display: flex; align-items: center; gap: .75rem; margin: 0; padding: .4rem .75rem; color: var(--muted); font-size: .9rem; min-width: 0; }
.whoami span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidenav-foot form { margin: 0; }
.iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 2.5rem; height: 2.5rem; border-radius: var(--radius); background: none; border: 0; color: var(--fg); cursor: pointer; padding: 0; }
.iconbtn:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); }
.only-drawer { display: none; }

/* Rail: labels stay in the accessibility tree, hidden visually. */
html[data-nav="rail"] .sidenav { padding-inline: .5rem; }
html[data-nav="rail"] .sidenav-head { justify-content: center; padding-inline: 0; }
html[data-nav="rail"] .brand img { width: 2.75rem; height: 2.75rem; }
html[data-nav="rail"] .brand-text,
html[data-nav="rail"] .navlist a > span,
html[data-nav="rail"] .navbtn > span,
html[data-nav="rail"] .navgroup > span,
html[data-nav="rail"] .whoami > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
html[data-nav="rail"] .navlist a, html[data-nav="rail"] .navbtn, html[data-nav="rail"] .whoami { justify-content: center; padding-inline: 0; }
html[data-nav="rail"] #nav-toggle .icon { transform: rotate(180deg); }

/* A filter that takes a set: a disclosure styled as the drop-down it replaces, holding a
   checkbox each. No script, and the summary says what is ticked (NAF, 2026-09-19). */
.filter { position: relative; }
.filter > summary {
  list-style: none; cursor: pointer; white-space: nowrap;
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .45rem 2rem .45rem .7rem; background: var(--bg);
}
.filter > summary::-webkit-details-marker { display: none; }
.filter > summary::after {
  content: "\25be"; position: absolute; right: .7rem; top: .45rem; color: var(--muted);
}
.filter[open] > summary { border-color: var(--accent); }
.filter > fieldset {
  position: absolute; z-index: 20; top: calc(100% + .25rem); left: 0; min-width: 100%;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg);
  padding: .5rem .7rem; box-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .12); margin: 0;
  /* As wide as its longest label, and every row starting at the same edge: the panel sits in a
     flex row, and without saying so its rows were ragged down the right (NAF, 2026-09-19). */
  min-width: max-content; display: flex; flex-direction: column; align-items: stretch;
  text-align: left;
}
/* The panel is as wide as its longest label, so the box stays beside the word rather than
   wrapping above it (NAF, 2026-09-19). */
.filter .check { margin: .2rem 0; flex-wrap: nowrap; white-space: nowrap; justify-content: flex-start; }
@media (max-width: 40rem) {
  /* No room to float a panel over a phone: it opens in the flow and pushes the table down. */
  .filter > fieldset { position: static; box-shadow: none; }
}

.content { padding: 1.75rem clamp(1rem, 4vw, 3rem) 3rem; max-width: 72rem; width: 100%; }
/* 72rem is a measure for reading prose. A page whose point is a wide table takes the window it
   is given, because the alternative is scrolling a table sideways inside a half-empty screen. */
.content.wide { max-width: none; }

/* Under 60rem: a top bar with the menu button; the sidebar becomes an off-canvas drawer. */
@media (max-width: 60rem) {
  .shell { display: block; }
  .topbar { display: flex; align-items: center; gap: .5rem; padding: calc(.5rem + env(safe-area-inset-top, 0px)) .75rem .5rem; border-bottom: 1px solid var(--line); background: var(--side); position: sticky; top: 0; z-index: 20; }
  .topbar .brand img { width: 2.25rem; height: 2.25rem; }
  .only-desktop { display: none; }
  html.js .only-drawer { display: inline-flex; }
  html.js .sidenav {
    position: fixed; inset: 0 auto 0 0; width: min(var(--sidebar-w), 85vw); height: 100svh;
    transform: translateX(-100%); transition: transform .2s ease; z-index: 40;
    box-shadow: var(--shadow);
  }
  html.js .sidenav.open { transform: none; }
  html.js .backdrop { position: fixed; inset: 0; background: rgb(0 0 0 / .45); z-index: 30; }
  html.js .backdrop[hidden] { display: none; }
  html.no-js .sidenav { position: static; height: auto; border-right: 0; border-bottom: 1px solid var(--line); }
  html.no-js .topbar .iconbtn { display: none; }
  /* The rail preference is irrelevant in a drawer. */
  html[data-nav="rail"] .brand-text, html[data-nav="rail"] .navlist a > span, html[data-nav="rail"] .navbtn > span, html[data-nav="rail"] .navgroup > span, html[data-nav="rail"] .whoami > span { position: static; width: auto; height: auto; overflow: visible; clip: auto; white-space: normal; }
  html[data-nav="rail"] .navlist a, html[data-nav="rail"] .navbtn, html[data-nav="rail"] .whoami { justify-content: flex-start; padding-inline: .75rem; }
  html[data-nav="rail"] .brand img { width: 3.5rem; height: 3.5rem; }
  .content { padding: 1.25rem 1rem 2.5rem; }
}
@media (prefers-reduced-motion: reduce) { .sidenav { transition: none; } }
body.drawer-open { overflow: hidden; }

/* ----------------------------------------------------- signed-out pages */
.plain { flex: 1; display: flex; flex-direction: column; }
.plain > .messages { max-width: 40rem; width: 100%; margin: 1rem auto 0; padding: 0 1rem; }
.plain > :not(.auth):not(.messages) { max-width: 40rem; width: 100%; margin-left: auto; margin-right: auto; padding: 0 1rem; }
.plain > h1:not(.auth) { margin-top: 2rem; }

.auth { flex: 1; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.auth-hero {
  background: var(--brand); color: var(--brand-fg);
  display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center;
  padding: 3rem 2rem; gap: .25rem;
}
.auth-logo { width: clamp(9rem, 40%, 14rem); height: auto; background: #ffffff; border-radius: 50%; padding: 1.1rem; box-shadow: var(--shadow); margin-bottom: 1.5rem; }
.auth-callsign { margin: 0; font-size: 2.4rem; font-weight: 800; letter-spacing: .06em; line-height: 1.1; }
.auth-clubname { margin: 0; font-size: 1.15rem; opacity: .92; max-width: 22ch; }
.auth-card { display: grid; align-content: center; justify-content: center; grid-template-columns: minmax(0, 22rem); gap: 1rem; padding: 3rem 1.5rem; }
.auth-card h1 { margin-bottom: .25rem; }
.auth-card form { display: contents; }
.auth-card .stack { display: flex; flex-direction: column; gap: .9rem; }
.auth-links { margin: 0; font-size: .95rem; text-align: center; }
.authed { max-width: 30rem; }
.authed .auth-card { display: block; padding: 0; }
@media (max-width: 48rem) {
  .auth { grid-template-columns: 1fr; }
  .auth-hero { padding: 2rem 1.25rem 1.75rem; }
  .auth-logo { width: 7.5rem; padding: .75rem; margin-bottom: 1rem; }
  .auth-callsign { font-size: 1.8rem; }
  .auth-card { padding: 2rem 1.25rem 2.5rem; }
}

/* --------------------------------------------------------------- footer */
/* The page is drawn edge to edge (viewport-fit=cover in base.html), so every edge that meets
   a system bar pays for it here. In a browser the insets are zero and nothing moves; in an
   installed app they keep the footer clear of the navigation bar and the top bar clear of the
   status bar. */
footer.site {
  background: var(--brand); color: var(--brand-fg); text-align: center;
  padding: 1.25rem calc(1rem + env(safe-area-inset-right, 0px))
           calc(1.35rem + env(safe-area-inset-bottom, 0px))
           calc(1rem + env(safe-area-inset-left, 0px));
}
footer.site p { margin: 0; }
footer.site .club { font-weight: 600; font-size: 1.05rem; }
footer.site .club a { color: inherit; text-decoration: none; }
footer.site .club a:hover, footer.site .credit a:hover { text-decoration: underline; }
footer.site .credit { font-size: .85rem; opacity: .82; margin-top: .3rem; }
footer.site .credit a { color: inherit; }

/* --------------------------------------------------------- components */
.card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.25rem; margin: 1rem 0; }
.card.highlight { border-color: var(--accent); }
.card.narrow { max-width: 28rem; margin: 2rem auto; }
.card > h2:first-child { margin-top: 0; }
.list { padding-left: 1.25rem; } .list li { margin: .25rem 0; }
.table { width: 100%; border-collapse: collapse; margin: .5rem 0 1rem; }
.table th, .table td { text-align: left; padding: .55rem .5rem; border-bottom: 1px solid var(--line); vertical-align: top; }
.table thead th { border-bottom: 2px solid var(--line); font-size: .9rem; color: var(--muted); font-weight: 600; }
.roster tr.st-not_viable th { border-left: 4px solid var(--bad); }
.roster tr.st-at_risk th { border-left: 4px solid var(--warn); }
.roster tr.st-viable th { border-left: 4px solid var(--ok); }
.roster tr.st-empty th { border-left: 4px solid var(--line); }
.status { font-weight: 600; } .status.viable { color: var(--ok); } .status.at_risk { color: var(--warn); } .status.not_viable { color: var(--bad); }
.tag { display: inline-block; font-size: .8rem; padding: 0 .45rem; border-radius: .3rem; background: var(--card); border: 1px solid var(--line); }
.tag.ok { background: var(--tag-ok); } .tag.warn { background: var(--tag-warn); } .tag.bad { background: var(--tag-bad); } .tag.note { background: var(--tag-warn); }
.messages { list-style: none; padding: 0; margin: 0 0 1rem; }
.messages:focus { outline: none; }
.msg { padding: .65rem .9rem; border-radius: var(--radius); border: 1px solid var(--line); border-left-width: 4px; margin: .5rem 0; background: var(--card); }
.msg .lvl { font-weight: 700; }
/* Four levels, each with a word and a tint. Color never carries the meaning alone (FR-62). */
.msg.success { border-left-color: var(--ok); background: var(--tag-ok); border-color: var(--ok); }
.msg.error { border-left-color: var(--bad); background: var(--tag-bad); border-color: var(--bad); }
.msg.warning { border-left-color: var(--warn); background: var(--tag-warn); border-color: var(--warn); }
.msg.info { border-left-color: var(--accent); background: var(--accent-soft); border-color: var(--accent); }
.dl dt { font-weight: 600; margin-top: .5rem; } .dl dd { margin: 0; }
.prose h1, .prose h2, .prose h3 { margin-top: 1rem; }
.secret code { font-size: 1.5rem; letter-spacing: .05em; }

/* ------------------------------------------------------------- forms */
button, .button {
  background: var(--accent); color: var(--accent-fg); border: 0; border-radius: var(--radius);
  padding: .6rem 1.1rem; font: inherit; font-weight: 600; cursor: pointer; text-decoration: none; display: inline-block;
}
button:hover, .button:hover { filter: brightness(1.08); }
button.secondary { background: transparent; color: var(--accent); border: 1px solid var(--accent); }
button.secondary:hover { background: var(--accent-soft); filter: none; }
button.big { font-size: 1.5rem; padding: 1rem 2rem; width: 100%; }
button.wide, .button.wide { width: 100%; display: block; text-align: center; box-sizing: border-box; }
/* Controls on the library's own pages sit next to each other with no wrapper of their own. */
.libctl { margin: .35rem .6rem .35rem 0; }
.linklike { color: var(--accent); text-decoration: none; background: none; border: 0; padding: 0; font: inherit; cursor: pointer; }
.linklike:hover { text-decoration: underline; }
button.linklike.danger, .linklike.danger { background: none; color: var(--bad); padding: 0; font-weight: inherit; text-decoration: underline; }
button.linklike.danger:hover, .linklike.danger:hover { background: none; filter: none; text-decoration: underline; }
input, select, textarea {
  font: inherit; padding: .55rem .7rem; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg); color: var(--fg); width: 100%; max-width: 28rem;
}
input:focus, select:focus, textarea:focus { border-color: var(--accent); outline-color: var(--focus); }
input[type="checkbox"], input[type="radio"] { width: 1.15rem; height: 1.15rem; accent-color: var(--accent); margin: 0; }
form > div { margin: .75rem 0; }
form label, .field label { display: block; font-weight: 600; margin-bottom: .25rem; }
.stack { display: flex; flex-direction: column; gap: .9rem; }
.stack > div, .stack > fieldset { margin: 0; }
.field { min-width: 0; }
.check { display: flex; align-items: center; gap: .55rem; }
.check label { display: inline; font-weight: 500; margin: 0; }
/* A switch, its label, and its one line of help: the help goes under, on its own row. */
.check { flex-wrap: wrap; }
.check > .helptext { flex-basis: 100%; margin-left: 1.7rem; margin-top: 0; }
.row { display: flex; gap: 1rem; flex-wrap: wrap; } .row > * { flex: 1 1 10rem; }
/* A field whose whole content is one checkbox sits beside fields that have a label above their
   input. Without this it floats level with the labels instead of level with the inputs. */
.row > .field > .check:only-child { margin-top: 1.7rem; }
/* A row-level action after an item in a list needs air, or "Run Remove" reads as one phrase. */
.list > li > form.inline { margin-left: .5rem; }
fieldset { border: 1px solid var(--line); border-radius: var(--radius); padding: .75rem 1rem 1rem; margin: 0; }
legend { font-weight: 600; padding: 0 .35rem; }
fieldset .helptext { margin-top: 0; }
.addresses .addr { display: grid; grid-template-columns: minmax(0, 28rem) auto; gap: .5rem 1rem; align-items: end; margin-top: .75rem; }
.addresses .addr .check { padding-bottom: .6rem; }
@media (max-width: 40rem) { .addresses .addr { grid-template-columns: 1fr; align-items: start; } .addresses .addr .check { padding-bottom: 0; } }
.copyrow { display: flex; gap: .5rem; flex-wrap: wrap; } .copyrow input { flex: 1 1 16rem; }
/* The same row inside a table cell, where 16rem would push the other columns off, and
   the copy control as a glyph rather than a word: it is beside the thing it copies, so
   the label is for a screen reader (NAF, 2026-09-20, after GitHub's). */
.table .copyrow input { flex: 1 1 10rem; min-width: 0; font-size: var(--t-small); }
.copyrow.tight { flex-wrap: nowrap; gap: .25rem; align-items: center; }
.copyrow.tight .iconbtn { width: 2rem; height: 2rem; flex: none; color: var(--muted); }
.copyrow.tight .iconbtn:hover { color: var(--fg); }
.copyrow.tight .iconbtn.copied { color: var(--ok); }
form.inline { display: inline-flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.helptext { display: block; color: var(--muted); font-size: .9rem; margin-top: .25rem; }
.req { color: var(--bad); font-weight: 700; }
/* The settings page names each value in words; the key stays for whoever edits club.yaml. */
.settingkey { font-size: .8em; }
label .req { margin-left: .15rem; }
.errorlist { color: var(--bad); margin: .25rem 0; padding-left: 1rem; }
/* A field that came back wrong: the message sits with the field, and the field itself is marked. */
.fielderror { display: block; color: var(--bad); font-weight: 600; font-size: .9rem; margin-top: .25rem; }
.field.haserror > input, .field.haserror > select, .field.haserror > textarea { border-color: var(--bad); border-width: 2px; }
[hidden] { display: none !important; }

/* ------------------------------------------------- narrow tables */
@media (max-width: 40rem) {
  .table, .table thead, .table tbody,
  .table tr, .table th, .table td { display: block; }
  .table thead { position: absolute; left: -999px; }
  .table tr { border: 1px solid var(--line); border-radius: var(--radius); margin: .5rem 0; padding: .25rem; }
  .table td, .table th { border: 0; padding: .25rem .5rem; }
  /* Stacked, a cell has lost its column heading. data-label puts it back; a table with a
     header row and no data-label is unreadable here, which is what tools/check_interface.sh
     refuses. Every table stacks: one wide enough to want sideways scrolling instead overflowed
     the page at 390px, which FR-95 forbids. */
  .table td[data-label]::before {
    content: attr(data-label) ": "; font-weight: 600; color: var(--muted);
  }
  .table td:empty { display: none; }
}

/* ------------------------------------------- management pages */
.titlerow { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.titlerow h1 { margin-bottom: .5rem; }
.crumbs { margin: 0 0 .5rem; font-size: .9rem; color: var(--muted); }
.cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); gap: 1rem; align-items: start; }
.cols > .card, .cols > .stack > .card { margin: 0; }
.searchrow { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: 1rem; }
/* The search box grows; a checkbox inside a filter panel is not a search box, and this rule
   used to stretch each one across its row, leaving the labels ragged down the right edge
   (found by the advisor, 2026-09-19). */
.searchrow > input[type="search"] { max-width: 24rem; flex: 1 1 14rem; }
.directory { columns: 14rem; list-style: none; padding: 0; } .directory li { padding: .25rem 0; break-inside: avoid; }
button.danger { background: var(--bad); color: var(--bad-fg); }
button.danger:hover { filter: brightness(1.1); }
a.button.secondary { background: transparent; color: var(--accent); border: 1px solid var(--accent); }
.captain-actions { display: block; margin-top: .25rem; font-size: .9rem; }
.captain-actions .linklike { margin-right: .5rem; }
h3 { font-size: 1.05rem; margin: 1.25rem 0 .5rem; }

/* ------------------------------------------------------------ roster */
.when { margin: 0 0 .25rem; font-size: 1.05rem; }
.lede { font-size: 1.05rem; }
.banner { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; padding: .75rem 1rem; border-radius: var(--radius); background: var(--tag-warn); border: 1px solid var(--warn); margin: 1rem 0; }
.banner p, .banner form { margin: 0; }
/* The three banners meant three different things and all looked like a warning. Acting for
   somebody is a mode you chose; read-only is a fact about the account; viewing as somebody
   else is the one that should keep catching your eye. */
.banner.acting { background: var(--accent-soft); border-color: var(--accent); }
/* A standing question wears the message strip, because that is what it reads as. Its buttons
   sit on the same line as its sentence rather than beneath them. */
.messages.ask { margin-bottom: .5rem; }
.messages.ask .inline { display: inline; }
.messages.ask button { padding: .2rem .6rem; font-size: var(--t-small); margin-left: .25rem; }
.banner.readonly { background: var(--card); border-color: var(--line); }
.banner.impersonating { background: var(--tag-warn); border-color: var(--warn); }
/* A standing fact, quieter than a banner and never mistaken for something that just happened. */
.note { padding: .6rem .9rem; border-radius: var(--radius); background: var(--card); border: 1px solid var(--line); margin: 1rem 0; color: var(--fg); }
.note p:first-child { margin-top: 0; } .note p:last-child { margin-bottom: 0; }
.rosterhead { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-top: 1.5rem; }
.rosterhead h2, .rosterhead p { margin: 0; }
.legend { font-size: .85rem; margin: .5rem 0 .75rem; }
.tag.open { background: var(--accent-soft); color: var(--accent-soft-fg); border-color: transparent; }
.tag.ok { color: var(--ok); } .tag.warn { color: var(--warn); }
.health .tag { margin-right: .25rem; }

.roster { --cols: 1; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.roster .r-head, .roster .r-row { display: grid; grid-template-columns: 9.5rem repeat(var(--cols), minmax(0, 1fr)); }
.roster.grid .r-head, .roster.grid .r-row { grid-template-columns: 9.5rem repeat(auto-fit, minmax(0, 1fr)); }
.roster.list .r-row { grid-template-columns: 9.5rem minmax(0, 1fr); }
.roster .r-head { position: sticky; top: 0; z-index: 2; background: var(--side); border-bottom: 2px solid var(--line); font-size: .85rem; color: var(--muted); font-weight: 600; }
.roster .r-head > div { padding: .5rem .75rem; }
.roster .r-pos { display: flex; flex-direction: column; line-height: 1.2; } .roster .r-pos .loc { font-weight: 400; }
.roster .r-dayhead { background: var(--card); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: .35rem .75rem; font-weight: 600; position: sticky; top: 2.4rem; z-index: 1; }
.roster.list .r-dayhead, .roster .r-dayhead { top: 0; }
.roster .daysel { gap: .6rem; } .roster .daysel span { font-weight: 600; }
.roster .r-row { border-top: 1px solid var(--line); } .roster .r-day .r-row:first-of-type { border-top: 0; }
.roster .r-time { padding: .5rem .75rem; display: flex; flex-direction: column; gap: .1rem; line-height: 1.25; }
.roster .r-time .lead { font-weight: 600; font-variant-numeric: tabular-nums; }
.roster .r-time .other { font-size: .8rem; }
.roster .r-time .tag { align-self: flex-start; margin-top: .2rem; }
.roster .kind-setup, .roster .kind-breakdown { background: color-mix(in srgb, var(--card) 60%, transparent); }
.roster .r-cell { position: relative; border-left: 1px solid var(--line); min-height: 3.4rem; }
.roster .r-cell.empty { background: repeating-linear-gradient(45deg, transparent 0 6px, color-mix(in srgb, var(--line) 50%, transparent) 6px 7px); }
.roster .r-cell > input[type="checkbox"] { position: absolute; left: .6rem; top: .7rem; z-index: 1; }
.roster .slot-link { display: flex; flex-direction: column; gap: .15rem; padding: .5rem .75rem .5rem 2.3rem; min-height: 3.4rem; color: inherit; text-decoration: none; border-left: 4px solid transparent; }
.roster .slot-link:hover { background: color-mix(in srgb, var(--fg) 4%, transparent); }
.roster .tone-open .slot-link { border-left-color: var(--accent); }
.roster .tone-ok .slot-link { border-left-color: var(--ok); }
.roster .tone-warn .slot-link { border-left-color: var(--warn); }
.roster .tone-bad .slot-link { border-left-color: var(--bad); background: color-mix(in srgb, var(--bad) 8%, transparent); }
.roster .tone-muted .slot-link { border-left-color: var(--line); color: var(--muted); }
.roster .r-cell.mine { outline: 2px solid var(--accent); outline-offset: -2px; }
.roster .people { display: flex; flex-wrap: wrap; gap: .15rem .6rem; }
.roster .person { display: inline-flex; flex-direction: column; line-height: 1.15; }
.roster .person .role { color: var(--muted); font-size: .75rem; }
.roster .you { font-size: .75rem; font-weight: 700; color: var(--accent); align-self: center; }
.roster .state { font-size: .85rem; display: flex; gap: .4rem; align-items: baseline; flex-wrap: wrap; }
.roster .state .word { font-weight: 600; }
.roster .tone-open .state .word { color: var(--accent); } .roster .tone-ok .state .word { color: var(--ok); } .roster .tone-warn .state .word { color: var(--warn); } .roster .tone-bad .state .word { color: var(--bad); }
.roster .state small { color: var(--muted); }
.roster .r-where { font-size: .8rem; color: var(--muted); }

.bulkbar { position: sticky; bottom: 0; z-index: 5; display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; margin: 1rem 0 0; padding: .75rem 1rem; background: var(--side); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); }
.bulkbar input, .bulkbar select { width: auto; max-width: 18rem; }
html.js .bulkbar:not(.active) { display: none; }

.slot-dialog { border: 0; border-radius: var(--radius); padding: 1.25rem 1.5rem 1.5rem; max-width: min(40rem, 94vw); width: 100%; background: var(--bg); color: var(--fg); box-shadow: var(--shadow); }
.slot-dialog::backdrop { background: rgb(0 0 0 / .45); }
.slot-dialog .dialog-close { position: absolute; right: .5rem; top: .5rem; }
.slot-dialog h1, .slotpage h1 { font-size: 1.4rem; }
.slot-dialog h2, .slotpage h2 { font-size: 1.05rem; margin-top: 1.1rem; }
.slot-state { font-weight: 600; padding: .4rem .7rem; border-radius: var(--radius); display: inline-block; }
.slot-state.tone-open { background: var(--accent-soft); color: var(--accent-soft-fg); }
.slot-state.tone-ok { background: var(--tag-ok); } .slot-state.tone-warn { background: var(--tag-warn); } .slot-state.tone-bad { background: var(--tag-bad); }
.slot-state.tone-muted { background: var(--card); color: var(--muted); }
.slotpage { max-width: 40rem; }

@media (max-width: 48rem) {
  .roster.grid .r-head { display: none; }
  .roster .r-head, .roster .r-row, .roster.grid .r-row, .roster.list .r-row { grid-template-columns: 1fr; }
  .roster .r-time { flex-direction: row; gap: .6rem; align-items: baseline; padding-bottom: .25rem; }
  .roster .r-cell { border-left: 0; border-top: 1px dashed var(--line); }
  .roster .r-cell.empty { display: none; }
  .roster .r-dayhead { top: 0; }
}
@media print {
  .sidenav, .topbar, footer.site, .bulkbar, .legend, .zones a, .banner form, .titlerow .button, .crumbs, .roster input[type="checkbox"], .skip { display: none !important; }
  .shell { display: block; } .content { padding: 0; max-width: none; }
  .roster { border-color: #999; } .roster .slot-link { padding-left: .5rem; }
  .roster .r-head, .roster .r-dayhead { position: static; }
  a { color: inherit; text-decoration: none; }
}

/* ---------------------------------------------- times in two zones */
.when1, .when2 { display: block; line-height: 1.3; }
.when2 { font-size: .85rem; }
td .when1, th .when1 { font-weight: 600; }
.eventcards { list-style: none; padding: 0; margin: 0; display: grid; gap: .75rem; }
.eventcard { border: 1px solid var(--line); border-radius: var(--radius); padding: .85rem 1rem; background: var(--card); }
.eventcard-head { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.eventcard-title { font-weight: 600; font-size: 1.05rem; }
.eventcard .when { margin: .35rem 0 0; font-size: 1rem; }

/* FR-108: unread count in the sidebar; FR-82: unread rows in My messages */
.badge { display: inline-block; min-width: 1.4em; padding: 0 .4em; border-radius: 1em; font-size: .75rem; font-weight: 600; text-align: center; background: var(--brand); color: #fff; margin-left: .35em; }
.msglist .unread summary strong::before { content: "● "; color: var(--brand); }
.table-wrap { overflow-x: auto; }

/* FR-95: nothing forces a phone to scroll sideways. Long tokens (feed URLs, keys, JSON) wrap. */
code, .copyrow code, .kv dd, .helptext { overflow-wrap: anywhere; }
/* This used to be max-width: 100% for all three, which sat after the 28rem cap above and
   undid it, so every text field ran the full width of the page. */
textarea { max-width: 100%; }
input:not([type="checkbox"]):not([type="radio"]), select { max-width: min(28rem, 100%); }
pre { overflow-x: auto; max-width: 100%; }
.brand-preview { height: 48px; max-width: 12rem; object-fit: contain; background: #fff; border: 1px solid var(--line); border-radius: var(--radius); padding: .2rem; }

/* The levels ladder: each one named, described in a line, with what it can do underneath. */
.levels { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s2); }
.levels > li { padding: var(--s2) 0; border-top: 1px solid var(--line); }
.levels > li:first-child { border-top: 0; padding-top: 0; }
.levels .check { gap: var(--s1) var(--s2); flex-wrap: wrap; }
.levels label { font-weight: 600; }
.levels .helptext { margin: var(--s1) 0 0 1.7rem; }
.levels details { margin-left: 1.7rem; }
.levels summary { font-size: var(--t-small); color: var(--muted); cursor: pointer; }

/* A state and the one action that changes it, in a single control: the badge is the button. */
.tag-button {
  font: inherit; font-size: .8rem; line-height: 1.2; padding: .2rem .5rem; border-radius: 999px;
  border: 1px solid var(--line); background: var(--bg); color: var(--fg); cursor: pointer;
  width: auto;
}
.tag-button.warn { background: var(--tag-warn); border-color: var(--warn); }
.tag-button:hover { filter: brightness(.96); }
/* One address per block: the address, then what it does in words, then the actions. The row
   of badges, buttons, and links that used to follow each address read as noise. */
.addresses-list { list-style: none; padding-left: 0; }
.addresses-list > li { margin: 0; padding: var(--s2) 0; border-top: 1px solid var(--line); }
.addresses-list > li:first-child { border-top: 0; padding-top: 0; }
.addresses-list .address { font-weight: 600; overflow-wrap: anywhere; margin: 0; }
.addresses-list .addr-state { margin: var(--s1) 0 0; color: var(--muted); font-size: var(--t-small); }
.addresses-list .addr-state .needs { color: var(--warn); font-weight: 600; }
.addresses-list .addr-actions { display: flex; flex-wrap: wrap; gap: var(--s1) var(--s3); margin: var(--s1) 0 0; }
.addresses-list .addr-actions:empty { display: none; }
/* Django renders a multiple-checkbox field as a <ul>; it wants no bullets and no indent. */
.checkset > ul { list-style: none; margin: var(--s1) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--s1); }
.checkset > ul li { display: flex; align-items: center; gap: .55rem; }
.checkset > ul label { display: inline; font-weight: 500; margin: 0; }
.checkset > ul input { width: 1.15rem; }
/* Removing something in place: the confirmation is the disclosure, so it needs no second page
   and still works with JavaScript turned off. */
/* A sortable column heading. The whole heading is the control, and the arrow shows only on the
   column in force; aria-sort on the th tells a screen reader the same thing. */
.sortlink { color: inherit; text-decoration: none; display: inline-flex; align-items: baseline; gap: .3rem; }
.sortlink:hover { text-decoration: underline; }
.sortlink.here { color: var(--accent); }
.sortlink .arrow { font-size: .8em; }
/* A phone number broken across two lines reads as two numbers, and it is the one column here
   somebody copies by eye to dial. */
.table td[data-label="Phone"], .table td[data-label="Callsign"] { white-space: nowrap; }
@media (max-width: 40rem) { .table td[data-label="Phone"] { white-space: normal; } }

/* An empty state names what belongs there and how it gets there. Quiet, but not so quiet the
   reader takes it for a rendering fault. */
.empty { color: var(--muted); }
td.empty { padding: var(--s3) var(--s2); }
li.empty { list-style: none; margin-left: -1.25rem; }

/* The count of who an announcement reaches, with the control that counts again beside it. */
.note.recipients { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); flex-wrap: wrap; }
.note.recipients p { margin: 0; min-width: 0; }

/* Setting an event up is four things in order, on a page of a dozen cards. The strip says
   which are done and which one to do next, and each links to the card that does it. */
.steps { list-style: none; margin: 0 0 var(--s4); padding: 0; display: flex; flex-direction: column; gap: var(--s1); }
.steps > li { display: flex; align-items: flex-start; gap: var(--s2); padding: var(--s2); border-radius: var(--radius); border: 1px solid var(--line); background: var(--card); }
.steps > li.now { border-color: var(--accent); background: var(--accent-soft); }
.steps .mark { flex: none; display: flex; align-items: center; justify-content: center; width: 1.6rem; height: 1.6rem; border-radius: 50%; border: 1px solid var(--line); background: var(--bg); font-size: var(--t-small); font-weight: 700; }
.steps > li.done .mark { background: var(--tag-ok); border-color: var(--ok); color: var(--ok); }
.steps > li.done .what > a { color: var(--muted); }
.steps .what { min-width: 0; }
.steps .helptext { margin-top: .1rem; }
@media (min-width: 60rem) { .steps { flex-direction: row; } .steps > li { flex: 1 1 0; } }

/* Everything that cannot be undone, in one place at the foot of the page, marked as such. */
.card.dangerzone { border-color: var(--bad); }
.card.dangerzone > h2 { color: var(--bad); margin-top: 0; }
.card.dangerzone > details + details { margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--line); }

details.confirm { margin-top: var(--s1); }
details.confirm > summary { color: var(--bad); text-decoration: underline; cursor: pointer; font-size: var(--t-small); width: fit-content; }
details.confirm > p { margin: var(--s1) 0; font-size: var(--t-small); }
/* Adding an address: the field and its button sit on one line, and stack on a phone. */
.row-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .5rem; margin-top: .6rem; }
.row-form label { display: block; font-size: .9rem; }
.row-form input { min-width: 16rem; }

/* Approve and Decline read as one choice, so they sit on one row; the reason travels with the
   button it belongs to. They stack on a phone, and Approve stays first either way. */
.decide-row { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s1) var(--s3); margin-top: var(--s1); }
.decide-decline { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s1); }
.decide-decline input { min-width: 14rem; }
/* Declining opens in place inside the row, so the queue stays a list. */
.decide-row details.decide-decline { margin: 0; }
.decide-row details.decide-decline > summary { font-size: inherit; }
.decide-row details.decide-decline .row-form { margin-top: .4rem; }
/* Turning a decline round, from its own row in the log: an ordinary action rather than
   a destructive one, so it is not red. */
details.reverse > summary { color: var(--accent, inherit); text-decoration: none; white-space: nowrap; }
details.reverse .field { margin-top: .4rem; } details.reverse input { min-width: 12rem; }
@media (max-width: 560px) { .decide-decline input { min-width: 0; width: 100%; } }

/* The access-groups grid: one row per capability, one column per group. It scrolls sideways on a
   phone inside its own wrapper, so the page itself never does (FR-95). */
.tablewrap { overflow-x: auto; max-width: 100%; }
.table.grid th[scope="row"] { text-align: left; font-weight: 500; }
.table.grid td.tick { text-align: center; }
.table.grid td.tick input { width: 1.1rem; height: 1.1rem; }

/* The level a session is acting at, under the name in the sidebar. A link, because that is where
   it is changed (apps/accounts/acting.py). */
a.whoami { text-decoration: none; color: inherit; display: flex; }
.whoami a { color: inherit; text-decoration: none; }
.whoami a:hover { text-decoration: underline; }
/* The level under the name: smaller, and no dimmer than the muted color it already uses.
   An opacity on top of that took it below 4.5:1 against the sidebar. */
.whoami .level { display: block; font-size: .78rem; }
