/*
  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. It marks what can be acted on: actions, links, the
  active menu entry (--accent-soft). Everything else stands on white
  paper and one neutral --surface, never tinted from the club's color (docs/INTERFACE.md,
  "Color"). The one club-colored panel is the sign-in page's; in dark mode it stays a
  background and a lightened mix of the brand 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 a column holding the content and, under it, the footer.
  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 oklab, var(--brand) 9%, #ffffff);
  --accent-soft-fg: var(--brand);
  --bg: #ffffff; --fg: #1b1f24; --muted: #5a6470; --line: #d9dee5;
  /* Three neutral surfaces (SK, 2026-11-06, D1; docs/INTERFACE.md, "Color"): the canvas the
     signed-in column stands on, the white of a panel, a card, a field or a dialog, and the band
     of a panel's header and footer, the sidebar and a table's header row. --surface is that
     band too. --line-strong outlines what is clicked or typed in, at 3:1 or more on a panel
     and on the canvas (WCAG 1.4.11). */
  --canvas: #f1f3f6; --panel: #ffffff; --panel-head: #f8f9fb; --surface: var(--panel-head);
  --line-strong: #828c99;
  --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%235a6470' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");  /* a select's arrow, in --muted */
  --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;
  --tag-info: var(--accent-soft); --tag-neutral: #eceff3;
  /* Shape by hierarchy: the smaller and more numerous a thing, the squarer it is. A field or a
     tag, a button, and a card or anything else that stands on its own. */
  --r-field: 4px; --r-button: 6px; --r-card: 8px;
  /* 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: .875rem; --t-body: 1rem; --t-lede: 1.125rem; --t-h2: 1.25rem; --t-h1: 1.875rem;
  --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);
  --menu-shadow: 0 10px 30px rgb(16 24 40 / .14), 0 2px 6px rgb(16 24 40 / .08);
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #111418; --fg: #e8ecf1; --muted: #9aa5b1; --line: #2a323c;
    --canvas: #0d1014; --panel: #161a20; --panel-head: #1b2027; --line-strong: #667180;
    --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%239aa5b1' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    --accent: color-mix(in oklab, var(--brand) 42%, #ffffff);
    --accent-fg: #0c1220;
    --accent-soft: color-mix(in oklab, 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; --tag-neutral: #242a32;
    --shadow: 0 1px 2px rgb(0 0 0 / .4), 0 8px 24px rgb(0 0 0 / .35);
    --menu-shadow: 0 10px 30px rgb(0 0 0 / .5), 0 2px 6px rgb(0 0 0 / .4);
  }
}

/* ------------------------------------------------------------------ base */
/* Atkinson Hyperlegible Next (OFL, static/fonts/OFL.txt), two weights. Chosen for callsigns and
   times: its zero is slashed, so N0CAN never reads as NOCAN, and I, l and 1 all differ, where
   the system faces draw I and l as the same bar. A rule asking for 500 gets the 400 and one
   asking for 600 or 800 the 700; nothing is synthesized. See docs/INTERFACE.md, "Type". */
@font-face { font-family: "Atkinson Hyperlegible Next"; src: url("../fonts/AtkinsonHyperlegibleNext-Regular.16653d2ae0a9.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Atkinson Hyperlegible Next"; src: url("../fonts/AtkinsonHyperlegibleNext-Bold.219587669913.woff2") format("woff2"); font-weight: 700; font-display: swap; }
* { box-sizing: border-box; }
html { font: 16px/1.5 "Atkinson Hyperlegible Next", 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: var(--t-h1); line-height: 1.2; letter-spacing: -.01em; margin: 0 0 1rem; }
h2 { font-size: var(--t-h2); 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; }
/* Skip to content puts focus on <main> (tabindex=-1) so screen readers follow it; it is a
   container, not a control, so it takes no ring. */
#main:focus { outline: none; box-shadow: none; }
.icon { width: 1.25rem; height: 1.25rem; flex: none; }
.icon.small { width: 1rem; height: 1rem; }  /* the one a small button holds (#224) */

/* --------------------------------------------------------- app shell */
.shell { flex: 1; display: grid; grid-template-columns: var(--sidebar-w) minmax(0, 1fr); background: var(--panel-head); }  /* the sidebar's column all the way down, past the sticky sidebar */
html[data-nav="rail"] .shell { grid-template-columns: var(--rail-w) minmax(0, 1fr); }
.shell > .page { grid-column: 2; min-width: 0; display: flex; flex-direction: column; background: var(--canvas); box-shadow: -1px 0 0 var(--line); }
.page > main { flex: 1; }
/* The bar over every signed-in page, holding the UTC clock (#246). */
.pagebar { display: flex; justify-content: flex-end; padding: .5rem clamp(1rem, 4vw, 3rem) 0; }
.utc-clock { display: inline-flex; align-items: center; gap: .35rem; padding: .2rem .6rem; border: 1px solid var(--line); border-radius: var(--r-button); background: var(--panel); font-size: var(--t-small); font-weight: 700; font-variant-numeric: tabular-nums; }
@media print { .pagebar { display: none; } }
.page > .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(--surface); 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: var(--t-lede); letter-spacing: .01em; }
.brand-sub { color: var(--muted); font-size: var(--t-small); }

.navlist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .125rem; }
.navlist a, .navbtn {
  display: flex; align-items: center; justify-content: flex-start; gap: .75rem;
  padding: .55rem .75rem; border-radius: var(--r-button);
  color: var(--fg); text-decoration: none; font: inherit; font-weight: 400;
  background: none; border: 0; width: 100%; text-align: left; cursor: pointer;
}
.navlist a .icon, .navbtn .icon, .whoami .icon { color: var(--muted); }
.navlist a[aria-current="page"] { background: var(--accent-soft); color: var(--accent-soft-fg); font-weight: 700; }
.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: var(--t-small); font-weight: 700;
  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: var(--t-small); 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(--r-button); background: none; border: 0; color: var(--fg); cursor: pointer; padding: 0; }
.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(--r-button);
  padding: .45rem 2rem .45rem .7rem; background: var(--panel);
}
.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); }
/* A filter button (#229): dashed while it is off, solid once it narrows the list. */
.filter > summary { border-style: dashed; border-color: var(--line-strong); }
.filter.set > summary { border-style: solid; }
.filter > fieldset {
  position: absolute; z-index: 20; top: calc(100% + .25rem); left: 0; min-width: 100%;
  border: 1px solid var(--line); border-radius: var(--r-card); background: var(--panel);
  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(--surface); 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: var(--t-h1); font-weight: 700; letter-spacing: .06em; line-height: 1.1; }
.auth-clubname { margin: 0; font-size: var(--t-lede); 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; }
/* A long form (joining) scrolls past the panel; the panel stays in view, its mark centered. */
@media (min-width: 48.0625rem) { .auth-hero { position: sticky; top: 0; height: 100vh; align-self: start; } }
.auth-card .stack { display: flex; flex-direction: column; gap: .9rem; }
.auth-links { margin: 0; font-size: var(--t-body); 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: var(--t-h1); }
  .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. */
/* No background of its own, and in the page's column, not under the sidebar (SK, 2026-11-03):
   it closes the page rather than framing the window. */
footer.site {
  color: var(--fg); border-top: 1px solid var(--line); 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: 700; font-size: var(--t-lede); }
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: var(--t-small); color: var(--muted); margin-top: .3rem; }
footer.site .credit a { color: inherit; }

/* --------------------------------------------------------- components */
.card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-card); padding: 1.25rem; margin: 1rem 0; }
.card.highlight { border-color: var(--accent); }
.card.narrow { max-width: 28rem; margin: 2rem auto; }
/* A part of a page: its heading and what is under it, on the page itself, with a rule above
   the next part. A card is only for a thing that stands on its own, such as a message or an
   event in a list (docs/INTERFACE.md, "Shape"). */
/* A section is a panel without a header band (D1, SK 2026-11-06; #242 to #245): every page's
   parts stand on the canvas as white boxes, the pages not yet given panel headers included. A
   section inside a panel or a card is a plain part of it again. */
.section { margin: 0 0 var(--s3); padding: 1rem 1.25rem; background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-card); min-width: 0; }
:is(.panel, .card, .section) .section { margin: 0; padding: var(--s3) 0 0; background: none; border: 0; border-radius: 0; }

/* Panels (#227, SK 2026-11-06, D1; docs/INTERFACE.md, "Shape"): one part of a page with a job,
   white on the canvas. A header names it and holds its actions at the right, with one line
   under the name if it needs one; a body; sometimes a footer with a note at the left and the
   save at the right. Markup, so the heading stays a real h2 or h3:
     <section class="panel" aria-labelledby="x">
       <header class="panel-head"><div><h2 id="x">Name</h2><p>One line.</p></div>
         <div class="actions">buttons</div></header>
       <div class="panel-body">…</div> or <ul class="rows">…</ul>
       <footer class="panel-foot"><p>A note.</p><button>Save</button></footer>
     </section> */
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-card); margin: 0 0 var(--s3); min-width: 0; }
.panel-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--s2) var(--s3); padding: 1rem 1.25rem; }
.panel-head :is(h2, h3) { font-size: var(--t-lede); margin: 0; }
.panel-head p { margin: .2rem 0 0; color: var(--muted); font-size: var(--t-small); max-width: 40rem; }
.panel-head + * { border-top: 1px solid var(--line); }
.panel > :is(.panel-body, .rows, form) + :is(.panel-body, .rows, form) { border-top: 1px solid var(--line); }
.panel-body { padding: 1rem 1.25rem; }
.panel-body > :first-child { margin-top: 0; } .panel-body > :last-child { margin-bottom: 0; }
.panel-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s2) var(--s3); padding: .75rem 1.25rem; border-top: 1px solid var(--line); background: var(--panel-head); border-radius: 0 0 var(--r-card) var(--r-card); }
.panel-foot p { margin: 0; color: var(--muted); font-size: var(--t-small); }
/* A form that holds a panel's body and footer: no gap between them, and one rule, not two, when
   it opens straight onto its footer (Publish). */
.panel > form.stack { gap: 0; }
.panel-head + form > .panel-foot:first-child { border-top: 0; }
.panel-foot .check { margin: 0; }
.panel-foot > :last-child { margin-left: auto; }
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
/* A list of rows inside a panel, each with its actions at the end; a phone puts them under. */
ul.rows { list-style: none; margin: 0; padding: 0; }  /* ul: over .list, which a list of rows may also carry */
ul.rows > li { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s2); padding: .75rem 1.25rem; }
ul.rows > li { margin: 0; }
ul.rows > li + li { border-top: 1px solid var(--line); }
.rows > li > .grow { flex: 1 1 12rem; min-width: 0; }
.rows .sub { display: block; color: var(--muted); font-size: var(--t-small); }
ul.rows > li > form.inline { margin-left: auto; }
/* A group of rows under a line of its own inside a panel: a location and its positions. */
.group + .group { border-top: 1px solid var(--line); }
.group-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s2); padding: .75rem 1.25rem; background: var(--panel-head); }
.group-head .sub { display: block; color: var(--muted); font-size: var(--t-small); }
.group-head > .grow { flex: 1 1 12rem; min-width: 0; }
.group .rows > li { padding-left: 2rem; }
.row-ic { display: inline-flex; align-items: center; justify-content: center; width: 1.9rem; height: 1.9rem; border-radius: var(--r-button); background: var(--canvas); color: var(--muted); flex: none; }

/* ⋮ menus (#228, D7; technical addendum A10): a popover under its button, a list of links and
   buttons, the destructive ones last and red. The disclosure pattern, not role="menu", so no
   arrow keys to learn: Tab moves through it, Escape closes it.
     <button type="button" class="ghost small icon-only" popovertarget="m-1" aria-label="More actions for Run">{% icon 'more' 'small' %}</button>
     <div class="menu" id="m-1" popover> <a href="…">{% icon 'copy' 'small' %}Duplicate</a> … </div> */
.menu {
  margin: .25rem 0 0; padding: .3rem; min-width: 12rem; border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--panel); color: var(--fg); box-shadow: var(--menu-shadow);
  position-area: block-end span-inline-start; position-try-fallbacks: flip-block, flip-inline;
}
.menu form { margin: 0; }
.menu :is(a, button) {
  display: flex; align-items: center; justify-content: flex-start; gap: .6rem; width: 100%; padding: .5rem .65rem;
  border: 0; border-radius: var(--r-button); background: none; color: var(--fg);
  font: inherit; font-weight: 400; text-align: left; text-decoration: none; cursor: pointer;
}
.menu :is(a, button).danger { color: var(--bad); background: none; }
.menu hr { border: 0; border-top: 1px solid var(--line); margin: .3rem 0; }

/* A toolbar above a panel's table (#229): search, filters, and the count at the right. */
.toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; padding: .75rem 1.25rem; }
.toolbar .count { margin-left: auto; color: var(--muted); font-size: var(--t-small); }
/* On one line with what it names (SK, 2026-11-10): a form label's space under it lifted the
   label off the line, and a select as wide as a form's pushed the button and the count away. */
.toolbar > label { margin: 0; }
.toolbar > select { width: auto; }
/* A disabled button (Send to 0 on Announce): faded, as Material 3 fades a disabled control, and
   with no state layer. WCAG 1.4.3 exempts an inactive control from contrast. */
button:disabled { opacity: .45; pointer-events: none; }
/* Where Publish cannot be done yet, the line saying why stands in its place (SK, 2026-11-10), so
   it reads as body text rather than as a footer's help. */
.panel-foot .not-ready { color: var(--fg); font-size: var(--t-body); }
.search { position: relative; flex: 1 1 14rem; max-width: 24rem; }
.search > .icon { position: absolute; left: .65rem; top: 50%; transform: translateY(-50%); color: var(--muted); pointer-events: none; }
.search > input { padding-left: 2.2rem; max-width: none; }
/* A switch (#229): two or three choices with counts, links with aria-current, so it is an address
   and works with JavaScript off ("All slots 50", "Needs someone 2"). */
.switch { display: inline-flex; flex-wrap: wrap; gap: .2rem; padding: .2rem; background: var(--panel-head); border: 1px solid var(--line); border-radius: var(--r-button); }
.switch a { display: inline-flex; align-items: center; gap: .4rem; padding: .3rem .7rem; border-radius: var(--r-button); color: var(--muted); font-size: var(--t-small); font-weight: 700; text-decoration: none; }
.switch a:hover { color: var(--fg); }
.switch a[aria-current] { background: var(--panel); color: var(--fg); box-shadow: 0 1px 2px rgb(0 0 0 / .08); }
.switch .n { font-weight: 400; }

/* A data table in a panel (#230, D5): quiet headings on the band, a divider per row, a tint under
   the pointer, the row's one link (its name) underlined only under the pointer and on focus, a
   second line for secondary facts, and the actions at the end. Phones stack it as any .table. */
.table.data { margin: 0; }
.table.data th, .table.data td { padding: .65rem .75rem; vertical-align: middle; }
.table.data :is(th, td):first-child { padding-left: 1.25rem; }
.table.data :is(th, td):last-child { padding-right: 1.25rem; }
.table.data thead th { background: var(--panel-head); border-bottom: 1px solid var(--line); }
.table.data tbody tr:last-child td { border-bottom: 0; }
.table.data tbody tr:hover { background: color-mix(in srgb, var(--fg) 3%, transparent); }
.table.data tbody :is(td, th) a:not(.button) { font-weight: 700; text-decoration: none; }
.table.data tbody :is(td, th) a:not(.button):is(:hover, :focus-visible) { text-decoration: underline; }
.table.data .sub { display: block; color: var(--muted); font-size: var(--t-small); font-weight: 400; overflow-wrap: anywhere; }
.table.data td.row-actions { text-align: right; white-space: nowrap; }
.section .table.data :is(th, td):first-child { padding-left: .5rem; }  /* the section's own padding is the inset */
.section .table.data :is(th, td):last-child { padding-right: .5rem; }
/* The event's header and tabs (#232, D8): the title and its state badge, a line of facts, a
   captain's actions at the right, and Roster, Manage and Who took part under it. */
.event-head { margin-bottom: var(--s3); }
.event-head-row { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--s2) var(--s3); }
.event-head-main { flex: 1 1 28rem; min-width: 0; }  /* the actions sit at the right while there is room */
.event-head-row > .actions { flex: none; }
.title-line { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .75rem; }
.title-line h1 { margin: 0; }
.facts { display: flex; flex-wrap: wrap; gap: .35rem 1.25rem; margin: .5rem 0 0; color: var(--muted); font-size: var(--t-small); }
.facts:empty { display: none; }
.facts > span { display: inline-flex; align-items: center; gap: .4rem; }
/* The line is an inset shadow, painted under the tabs, so the current tab's underline covers it
   without a -1px margin: in a box that scrolls sideways that margin overflowed it by a pixel and
   drew a vertical scrollbar (SK, 2026-11-10). */
.tabs { display: flex; gap: .25rem; margin-top: var(--s3); box-shadow: inset 0 -1px var(--line); overflow-x: auto; }
.tabs a { display: inline-flex; align-items: center; gap: .45rem; padding: .55rem .8rem; border-bottom: 2px solid transparent; color: var(--muted); font-weight: 700; text-decoration: none; white-space: nowrap; }
.tabs a:hover { color: var(--fg); }
.tabs a[aria-current] { color: var(--fg); border-bottom-color: var(--accent); }
.k-icons { display: grid; grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); }
.k-icons > li + li { border-top: 0; }
:is(.card, .section) > :nth-child(1 of :not(input[type=hidden])) { margin-top: 0; }  /* a form's hidden fields draw nothing */
:is(.card, .section) > :last-child { margin-bottom: 0; }  /* the padding is the space under it */
/* ...and a margin one level down collapses through its parent into that space: a table in its
   wrapper, the last line of a message, the last paragraph of rich text. */
:is(.card, .section) > .table-wrap:last-child > .table, :is(.card, .section) > details:last-child > :last-child,
:is(.card, .section) .prose:last-child > :last-child { margin-bottom: 0; }
.list { padding-left: 1.25rem; } .list li { margin: .25rem 0; }
.table { width: 100%; border-collapse: collapse; margin: .5rem 0 1rem; }
/* Desktop density (#146): rows a little tighter than a form's, so a list of members or messages
   shows more of itself. Under 40rem the stacked layout below sets its own padding. */
.table th, .table td { text-align: left; padding: .35rem .5rem; border-bottom: 1px solid var(--line); vertical-align: top; }
.table thead th { border-bottom: 2px solid var(--line); font-size: var(--t-small); color: var(--muted); font-weight: 700; }
.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: 700; } .status.viable { color: var(--ok); } .status.at_risk { color: var(--warn); } .status.not_viable { color: var(--bad); }
.tag { display: inline-flex; align-items: center; gap: .3rem; font-size: var(--t-small); font-weight: 700; line-height: 1.4; padding: .05rem .45rem; border-radius: var(--r-field); background: var(--tag-neutral); color: var(--muted); white-space: nowrap; }
/* A badge (#226): a tone and always a word, its word at 4.5:1 or more on its fill in both themes
   (apps/ops/tests/test_badges.py). Which state takes which tone is apps/ops/templatetags/badges.py. */
.tag.ok { background: var(--tag-ok); color: var(--ok); }
.tag.warn, .tag.note { background: var(--tag-warn); color: var(--warn); }
.tag.bad { background: var(--tag-bad); color: var(--bad); }
.tag:is(.info, .open) { background: var(--tag-info); color: var(--accent-soft-fg); }
.messages { list-style: none; padding: 0; margin: 0 0 1rem; }
.messages:focus { outline: none; }
.msg { padding: .65rem .9rem; border-radius: var(--r-card); border: 1px solid var(--line); border-left-width: 4px; margin: .5rem 0; background: var(--panel); }
.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); border-color: var(--accent); }
/* Label and value (#146): two columns at desktop, about half the height of the stack, with the
   label at the regular weight in the muted color; on a phone, the label above its value. */
.dl dt { color: var(--muted); margin-top: .5rem; } .dl dd { margin: 0; }
.dl dd > :first-child { margin-top: 0; } .dl dd > :last-child { margin-bottom: 0; }  /* rich text beside its label (#168) */
@media (min-width: 40rem) {
  .dl { display: grid; grid-template-columns: minmax(9rem, max-content) 1fr; gap: var(--s1) var(--s3); }
  .dl dt { margin-top: 0; }
}
.prose h1, .prose h2, .prose h3 { margin-top: 1rem; }
.secret code { font-size: var(--t-h2); letter-spacing: .05em; }

/* ------------------------------------------------------------- forms */
/* Buttons (#225; docs/INTERFACE.md, "Controls"): the primary in the club's color, one per form;
   the rest neutral (SK, 2026-11-06, D3). An icon goes before the word, and the button lays the
   two out in a row. Every variant works on <button> and on <a class="button">. */
button, .button {
  display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
  background: var(--accent); color: var(--accent-fg); border: 1px solid transparent; border-radius: var(--r-button);
  padding: .55rem 1.05rem; font: inherit; font-weight: 700; line-height: 1.5; cursor: pointer; text-decoration: none;
}
:is(button, .button).secondary { background: var(--panel); color: var(--fg); border-color: var(--line-strong); }
:is(button, .button).ghost { background: transparent; color: var(--fg); border-color: transparent; }
:is(button, .button).danger { background: var(--bad); color: var(--bad-fg); }
:is(button, .button).ghost.danger { background: transparent; color: var(--bad); }
:is(button, .button).danger-outline { background: var(--panel); color: var(--bad); border-color: var(--bad); }
:is(button, .button).small { padding: .3rem .65rem; font-size: var(--t-small); gap: .35rem; }
/* An icon alone, only for ⋮ and ?: square, and its aria-label shown under it on hover and focus,
   so the name is never only in a title a phone and the keyboard never show. */
:is(button, .button).icon-only { padding: .55rem; position: relative; }
:is(button, .button).icon-only.small { padding: .3rem; }
:is(button, .button).icon-only[aria-label]:is(:hover, :focus-visible)::after {
  content: attr(aria-label); position: absolute; top: calc(100% + .3rem); left: 50%; transform: translateX(-50%);
  z-index: 30; padding: .15rem .45rem; border-radius: var(--r-field); background: var(--fg); color: var(--panel);
  font-size: var(--t-small); font-weight: 400; white-space: nowrap; pointer-events: none;
}
/* State layers (Material 3): a veil of the control's own text color over its fill, 8% under the
   pointer and 10% focused or pressed, the same on every kind of control and with no JavaScript.
   A link-like button in a line of text keeps its underline instead. */
:is(button:not(.linklike), .button, .navlist a, .navbtn, .iconbtn, .filter > summary, .roster .slot-link, .tag-button, .menu a):hover {
  background-image: linear-gradient(color-mix(in srgb, currentColor 8%, transparent) 0 0);
}
:is(button:not(.linklike), .button, .navlist a, .navbtn, .iconbtn, .filter > summary, .roster .slot-link, .tag-button, .menu a):is(:focus-visible, :active) {
  background-image: linear-gradient(color-mix(in srgb, currentColor 10%, transparent) 0 0);
}
/* Link-buttons sit in a line of text, so when a row of them wraps on a phone the lines touched
   (Members, Member roster; SK, 2026-10-22). */
a.button { margin-block: .25rem; }
button.big { font-size: var(--t-h2); padding: 1rem 2rem; width: 100%; }
button.wide, .button.wide { width: 100%; display: flex; 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; }
.libctl.wide { margin: 1rem 0 0; max-width: 28rem; }  /* stacked under the field, as wide as it */
.linklike { display: inline; 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-strong); border-radius: var(--r-field);
  background: var(--panel); color: var(--fg); width: 100%; max-width: 28rem;
}
input:focus, select:focus, textarea:focus { border-color: var(--accent); outline-color: var(--focus); }
/* A select drawn by the page rather than the system. WebKit draws its own 23px button beside a
   44px text box, whatever height it is given, unless its appearance is none; the arrow is then
   ours, in the muted color, which holds 3:1 in both themes. In forced colors the image is dropped,
   so the system draws it again. */
select:not([multiple]) {
  appearance: none; padding-right: 2.2rem;
  background-image: var(--chevron); background-repeat: no-repeat;
  background-position: right .75rem center; background-size: .75rem;
}
@media (forced-colors: active) { select:not([multiple]) { appearance: auto; } }
/* htmx's request indicator, here rather than injected by htmx as a <style> (addendum A7). */
.htmx-indicator { opacity: 0; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { opacity: 1; transition: opacity 200ms ease-in; }
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: 400; margin-bottom: .25rem; }  /* a label is read, not shouted (#160) */
.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: 400; 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; }
/* Fields that are only a label and an input keep their inputs level when a label wraps: the text
   in each input sits on one line, whatever height an engine draws a select or a date. With help
   or checkboxes under a field, the row stays top-aligned, or the inputs would rise. */
.row:not(:has(.helptext, .check, .errorlist)) { align-items: last baseline; }
/* 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(--r-card); padding: .75rem 1rem 1rem; margin: 0; min-width: 0; }
/* Dates and times on a phone (SK, 2026-11-03): a fieldset grows to its widest content unless told
   otherwise (min-width above), and iPhone Safari draws a date or time box at a natural width that
   ignores width: 100%, so New event's first operating period ran off the screen. On iOS only,
   the native look goes, which lets the width hold; the height is kept, since an empty date box
   there would otherwise fold to nothing. */
input[type="date"], input[type="time"], input[type="datetime-local"] { min-width: 0; }
@supports (-webkit-touch-callout: none) {
  input[type="date"], input[type="time"], input[type="datetime-local"] { -webkit-appearance: none; appearance: none; min-height: 2.75rem; }
  input::-webkit-date-and-time-value { text-align: start; }
}
legend { font-weight: 700; 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; align-items: center; }  /* the text on the button's middle (SK, 2026-11-10) */ .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: var(--t-small); 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: var(--t-small); }
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: 700; font-size: var(--t-small); 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(--r-card); 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: 700; color: var(--muted);
  }
  .table td:empty { display: none; }
  .table.data td.row-actions { text-align: left; }
  .table.data td.row-actions::before { content: none; }  /* the buttons say what they do */
}

/* ------------------------------------------- management pages */
.titlerow { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
/* A list page's own header (#237): its heading, and its actions at the right, the primary last. */
.page-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s2) var(--s3); margin-bottom: var(--s3); }
.page-head h1 { margin: 0; }
.table.data tbody th[scope=row] { font-weight: 400; text-align: left; }
.titlerow h1 { margin-bottom: .5rem; }
.crumbs { margin: 0 0 .5rem; font-size: var(--t-small); 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; }
.captain-actions { display: block; margin-top: .25rem; font-size: var(--t-small); }
.captain-actions .linklike { margin-right: .5rem; }
h3 { font-size: var(--t-lede); margin: 1.25rem 0 .5rem; }

/* ------------------------------------------------------------ roster */
.when { margin: 0 0 .25rem; font-size: var(--t-lede); }
.lede { font-size: var(--t-lede); }
.banner { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; padding: .75rem 1rem; border-radius: var(--r-card); 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(--panel); 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(--panel); 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(--r-card); background: var(--panel); 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; }
/* The roster in a panel (#235): its header holds the zones, the switch and the Key; the strip on
   the band; the table edge to edge; the bulk bar as its footer. */
.roster-panel { margin-top: var(--s3); }
.roster-panel .rosterhead { margin: 0; align-items: center; }
.rosterhead .zones { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .75rem; }
.roster-panel .overview { margin: 0; background: var(--panel-head); }
.roster-panel .roster { border: 0; border-top: 1px solid var(--line); border-radius: 0; }
.roster-panel .bulkbar { margin: 0; border: 0; border-top: 1px solid var(--line); border-radius: 0 0 var(--r-card) var(--r-card); background: var(--panel-head); }
/* The slot dialog's people (#236): rows inside the dialog, flush with its edges. */
.slot-dialog .rows.people { margin: .5rem -.5rem; }
.slot-dialog .rows.people > li { padding: .6rem .5rem; align-items: flex-start; }
.slot-dialog .rows.people form.inline { margin: 0; }
.fold-more { display: inline-flex; align-items: center; gap: .3rem; padding: .2rem .5rem; border-radius: var(--r-button); color: var(--fg); font-weight: 700; font-size: var(--t-small); }
.r-fold:hover .fold-more { background: color-mix(in srgb, var(--fg) 7%, transparent); }
.rosterhead h2, .rosterhead p { margin: 0; }
.legend { font-size: var(--t-small); margin: .5rem 0 .75rem; }
/* The coverage strip beside the key (#161): a line a day, a mark per time span with its state as a
   symbol as well as a color (FR-62). The key then reads one entry a line. */
.overview { display: grid; grid-template-columns: minmax(0, 3fr) minmax(16rem, 2fr); gap: var(--s3); align-items: start; margin: .5rem 0 .75rem; }
.overview .legend { margin: 0; }
.overview .legend-label { display: block; margin-bottom: .25rem; }
.overview .legend li + li::before { content: none; }
/* The key in two columns (SK, 2026-11-10): each symbol centred in the first, the badge and the
   words in the second, a wrapped line hanging under the words. Every symbol is 1.25rem tall and
   sits on the middle of the first 1.5rem line; an empty box no longer rides on the baseline. */
.overview .legend ul { display: grid; grid-template-columns: auto 1fr; gap: .3rem .5rem; }
.overview .legend li { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; align-items: start; margin: 0; line-height: 1.5rem; }
.overview .legend li > :is(.sm, .mk) { justify-self: center; margin-top: .125rem; }
.overview .legend .what { grid-column: 2; }
.overview .legend .mk { display: inline-flex; align-items: center; justify-content: center; min-width: 1rem; height: 1.25rem; margin: 0; line-height: 1; }
.strip { display: flex; flex-direction: column; gap: .3rem; }
.strip-day { display: flex; gap: .5rem; align-items: center; }
.strip-label { flex: none; width: 3.5rem; font-size: var(--t-small); color: var(--muted); font-variant-numeric: tabular-nums; }
.strip-marks { display: flex; flex-wrap: wrap; gap: 2px; }
.sm { display: inline-flex; align-items: center; justify-content: center; width: 1rem; height: 1.25rem; font-size: var(--t-small); font-weight: 700; line-height: 1; text-decoration: none; border: 1px solid var(--line); border-radius: var(--r-field); background: var(--surface); color: var(--muted); }
.sm-open { border-color: var(--accent); color: var(--accent); background: var(--bg); }
.sm-covered { border-color: var(--ok); color: var(--ok); background: var(--tag-ok); }
.sm-needs { border-color: var(--warn); color: var(--warn); background: var(--tag-warn); }
.sm-problem { border-color: var(--bad); color: var(--bad); background: var(--tag-bad); }
/* An hour with no slot (#247), told apart by shape as well as tone: on the air with no slot yet is
   a dashed box; off the air is a hatched cell with no box at all. Neither is a link. */
.sm-none { border-style: dashed; border-color: var(--muted); background: var(--bg); }
.sm-off { border-color: transparent; background: repeating-linear-gradient(135deg, var(--line) 0 2px, transparent 2px 5px); }
@media (max-width: 48rem) { .overview { grid-template-columns: 1fr; } }
/* Desktop density (#146, with #161): rows a little tighter where a pointer is the usual way in. A
   slot keeps 44 px, a comfortable target; under 48rem the touch layout keeps its own height. */
@media (min-width: 48rem) {
  .roster .r-time, .roster .r-zone { padding-top: .35rem; padding-bottom: .35rem; }
  .roster .r-cell, .roster .slot-link { min-height: 2.75rem; }
  .roster .slot-link { padding-top: .35rem; padding-bottom: .35rem; }
  .roster .r-cell > input[type="checkbox"] { top: .5rem; }
}
/* The key is a labelled list: one line on a wide screen, one entry a line on a phone, where the
   run-on line was hard to read (SK, 2026-10-22). */
.legend-label { font-weight: 700; margin-right: .35rem; }
.legend ul { display: inline; list-style: none; padding: 0; margin: 0; }
.legend li { display: inline; }
.legend li + li::before { content: " · "; }
@media (max-width: 40rem) {
  .legend-label { display: block; margin-bottom: .25rem; }
  .legend li { display: block; margin: .2rem 0; }
  .legend li + li::before { content: none; }
}
/* The Key behind a ? beside Problems only, opening over the page (#169, SK 2026-11-02): a native
   popover. Without popover support the button and Close go, the key stays in place beside the
   strip as before; with it the strip has the width to itself. */
.keybtn { margin-left: .35rem; }  /* the Key's ?, an icon-only button since #235 */
.legend:popover-open { margin: auto; width: min(24rem, calc(100vw - 2rem)); max-height: calc(100vh - 2rem); overflow: auto; padding: var(--s3); border: 1px solid var(--line); border-radius: var(--r-card); background: var(--panel); color: var(--fg); box-shadow: 0 .5rem 2rem rgb(0 0 0 / .25); }
.legend:popover-open::backdrop { background: rgb(0 0 0 / .2); }
.keyclose { display: block; margin-top: .75rem; }
@supports selector(:popover-open) { .overview { grid-template-columns: 1fr; } }
@supports not selector(:popover-open) { .keybtn, .keyclose { display: none; } }
.msglist { list-style: none; padding-left: 0; }
.health .tag { margin-right: .25rem; }

.roster { --cols: 1; border: 1px solid var(--line); border-radius: var(--r-card); overflow: hidden; }
.roster .r-head, .roster .r-row { display: grid; grid-template-columns: 11rem repeat(var(--cols), minmax(0, 1fr)); }
.roster.grid .r-head, .roster.grid .r-row { grid-template-columns: 8rem repeat(auto-fit, minmax(0, 1fr)); }
.roster.list .r-row { grid-template-columns: 11rem minmax(0, 1fr); }
/* UTC is the axis; the event's own zone, where it differs, is a column of its own (#161). */
.roster.grid.twozones .r-head, .roster.grid.twozones .r-row { grid-template-columns: 8rem 9.5rem repeat(auto-fit, minmax(0, 1fr)); }
.roster.list.twozones .r-row { grid-template-columns: 11rem 11rem minmax(0, 1fr); }
.roster .r-zone { padding: .5rem .75rem; font-size: var(--t-small); line-height: 1.25; white-space: nowrap; font-variant-numeric: tabular-nums; }
.roster .r-head .r-zone { padding: .5rem .75rem; }
.roster .r-head { position: sticky; top: 0; z-index: 2; background: var(--surface); border-bottom: 2px solid var(--line); font-size: var(--t-small); color: var(--muted); font-weight: 700; }
.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(--surface); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: .35rem .75rem; font-weight: 700; 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: 700; }
.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: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.roster .r-time .tag { align-self: flex-start; margin-top: .2rem; }
.roster .kind-setup, .roster .kind-breakdown { background: color-mix(in srgb, var(--surface) 60%, transparent); }
.roster .r-cell { position: relative; border-left: 1px solid var(--line); min-height: 3.4rem; display: flex; flex-direction: column; }
/* The link fills its cell, so its tone line and its target run the row's full height where the
   time beside it is taller, as on a setup or breakdown row (SK, 2026-11-10). */
.roster .r-cell > .slot-link { flex: 1; }
.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.noselect .slot-link { padding-left: .75rem; }  /* no tick-box to make room for */
.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: var(--t-small); }
/* A credential, the control operator, or a sign-up's state, as a mark beside the role (#161,
   FR-65); its word is there for a screen reader, and the key gives it for everyone else. */
.mk { display: inline-block; margin-left: .15rem; padding: 0 .2rem; border: 1px solid var(--line); border-radius: var(--r-field); font-size: var(--t-small); font-weight: 700; line-height: 1.2; color: var(--fg); font-variant-numeric: tabular-nums; }
.mk.ok { border-color: var(--ok); color: var(--ok); }
.roster .you { font-size: var(--t-small); font-weight: 700; color: var(--accent); align-self: center; }
.roster .state { font-size: var(--t-small); display: flex; gap: .4rem; align-items: baseline; flex-wrap: wrap; }
.roster .state .word { font-weight: 700; }
.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: var(--t-small); color: var(--muted); }
/* A folded run of open hours (#161): one row across every position, its hours hidden after it
   (the [hidden] rule above, which is !important, outranks .r-row's display: grid). */
.roster .r-foldcell { grid-column: 2 / -1; } .roster.twozones .r-foldcell { grid-column: 3 / -1; }
.roster .fold-link { flex-direction: row; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: .2rem .6rem; }

.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(--surface); border: 1px solid var(--line); border-radius: var(--r-card); 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(--r-card); padding: 1.25rem 1.5rem 1.5rem; max-width: min(40rem, 94vw); width: 100%; background: var(--panel); 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: var(--t-h2); }
.slot-dialog h2, .slotpage h2 { font-size: var(--t-lede); margin-top: 1.1rem; }
.slot-state { font-weight: 700; padding: .4rem .7rem; border-radius: var(--r-field); display: inline-block; }
.slot-state.tone-open { background: var(--surface); color: var(--accent); }
.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(--surface); color: var(--muted); }
.slotpage { max-width: 40rem; }
/* One field width on a slot: a row of fields is as wide as one field, its fields sharing it. */
.slot-dialog .row, .slotpage .row { max-width: 28rem; }
.slot-dialog .row > *, .slotpage .row > * { flex-basis: 8rem; }
/* Manage's forms (#168): related fields in a group under a quiet heading, not a box in a box;
   whole numbers in boxes sized to what they hold; and what the generator will make, set apart
   above the buttons. */
fieldset.group { border: 0; padding: 0; margin: 0; min-width: 0; }
fieldset.group > legend { padding: 0; margin-bottom: .5rem; font-weight: 700; }
fieldset.group > .row > * { flex: 0 0 auto; }
.slot-dialog fieldset.group > .row { max-width: none; }  /* a group's few small boxes share a line */
input.num { width: calc(var(--num, 5) * 1ch + 1.6rem); }
input.num[size="1"], input.num[size="2"] { --num: 3; }
.preview { padding: .75rem 1rem; border: 1px solid var(--line); border-left: 4px solid var(--accent); border-radius: var(--r-card); background: var(--panel); }
.preview:empty { display: none; }
.preview strong { display: block; font-size: var(--t-lede); }

@media (max-width: 48rem) {
  .roster.grid .r-head { display: none; }
  .roster .r-head, .roster .r-row, .roster.grid .r-row, .roster.list .r-row,
  .roster.grid.twozones .r-row, .roster.list.twozones .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-foldcell, .roster.twozones .r-foldcell { grid-column: auto; }
  .roster .r-zone { padding: 0 .75rem .35rem; }
  .roster .r-dayhead { top: 0; }
}
@media print {
  .roster .r-row[hidden] { display: grid !important; } .roster .r-fold { display: none; }  /* every slot, on paper (FR-65, FR-85) */
  .sidenav, .topbar, footer.site, .bulkbar, .legend, .zones a, .keybtn, .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; }
/* The face's own figures are proportional, so 11:11Z is narrower than 20:09Z. Every digit the
   same width where numbers are read down a column or against each other: times and tables. */
.when, .when1, .when2, td { font-variant-numeric: tabular-nums; }
.when2 { font-size: var(--t-small); }
td .when1, th .when1 { font-weight: 700; }
.eventcards { list-style: none; padding: 0; margin: 0; display: grid; gap: .75rem; }
/* The events list in panels (#238): each event a row with a date tile, not a card of its own. */
.panel > .eventcards { display: block; }
.panel .eventcard { border: 0; border-radius: 0; padding: .85rem 1.25rem; background: none; }  /* centred like every row (SK, 2026-11-10) */
.panel .eventcard + .eventcard { border-top: 1px solid var(--line); }  /* the rows' divider, which border: 0 above had taken away (SK, 2026-11-10) */
.datetile { display: inline-flex; flex-direction: column; align-items: center; justify-content: center; width: 3rem; padding: .3rem 0; border: 1px solid var(--line); border-radius: var(--r-button); background: var(--panel-head); line-height: 1.1; flex: none; }
.datetile .d { font-weight: 700; font-size: var(--t-lede); }
.datetile .m { font-size: var(--t-small); color: var(--muted); }
.covline { margin: .4rem 0 0; display: flex; flex-wrap: wrap; gap: .3rem; }
.panel.quiet { background: var(--panel-head); }
/* Messages and Announcements (#241): each card a row of one panel, divided, not boxed. */
.msglist.panel { list-style: none; padding: 0; }
:is(.msglist.panel > li, .msgpanel > details).card { margin: 0; border: 0; border-radius: 0; background: none; padding: .85rem 1.25rem; }
:is(.msglist.panel > li + li, .msgpanel > details + details).card { border-top: 1px solid var(--line); }
.msglist .unread summary strong { font-weight: 700; }
.panel.quiet .eventcard-title { font-size: var(--t-body); }
.eventcard { border: 1px solid var(--line); border-radius: var(--r-card); padding: .85rem 1rem; background: var(--panel); }
.eventcard-head { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.eventcard-title { font-weight: 700; font-size: var(--t-lede); text-decoration: none; }  /* D5: a card's title */
.eventcard-title:is(:hover, :focus-visible) { text-decoration: underline; }
.eventcard .when { margin: .35rem 0 0; font-size: var(--t-body); }

/* 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: var(--r-card); font-size: var(--t-small); font-weight: 700; text-align: center; background: var(--brand); color: #fff; margin-left: .35em; }
/* The dot is for the eye (SK, 2026-11-10: "keep the dot"); "/ ''" keeps it from a screen reader,
   which hears the hidden "Unread:" before the subject instead (FR-62). */
.msglist .unread summary strong::before { content: "● "; content: "● " / ""; color: var(--brand); }  /* the first for a browser without the second */
.table-wrap { overflow-x: auto; position: relative; }  /* the containing block for what is
   positioned in a cell (a sort link's screen-reader words), so it scrolls with the table instead
   of widening the page: Access rosters scrolled sideways on CI's wider fonts (#157). */

/* 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(--r-field); 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 > li:last-child { padding-bottom: 0; }
.levels .check { gap: var(--s1) var(--s2); flex-wrap: wrap; }
.levels label { font-weight: 700; }
.levels .helptext { margin: var(--s1) 0 0 1.7rem; }
.levels details { margin-left: 1.7rem; }
.levels summary { font-size: var(--t-small); }

/* A state and the one action that changes it, in a single control: the badge is the button. */
.tag-button {
  font: inherit; font-size: var(--t-small); line-height: 1.2; padding: .2rem .5rem; border-radius: var(--r-button);
  border: 1px solid var(--line); background: var(--panel); color: var(--fg); cursor: pointer;
  width: auto;
}
.tag-button.warn { background: var(--tag-warn); border-color: var(--warn); }
/* 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 > li:last-child { padding-bottom: 0; }
.addresses-list .address { font-weight: 700; 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: 700; }
.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: 400; 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: var(--t-small); }
/* 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 (#172): one strip of steps drawn like the roster's coverage strip (.sm), a
   square mark per step, a check on green when done, the next step in the club's color. Required
   steps are numbered because they are a sequence; optional ones are not. Only the step you are on
   is a cell, in the pair the sidebar uses for its selected entry. */
.stages { margin: 0 0 var(--s3); background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-card); }  /* the bar in a panel (#233) */
.stages ol { list-style: none; margin: 0; padding: .4rem; display: flex; gap: 2px; }
.stages li { flex: 1 1 0; min-width: 0; }
.stages .cell { display: flex; gap: var(--s2); align-items: flex-start; height: 100%; padding: var(--s2); border-radius: var(--r-field); color: var(--fg); text-decoration: none; }
.stages a.cell:hover .nm { text-decoration: underline; }
.stages .mk { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 1.5rem; height: 1.75rem; border: 1px solid var(--line); border-radius: var(--r-field); background: var(--bg); font-size: var(--t-small); font-weight: 700; line-height: 1; color: var(--muted); }
.stages .txt { min-width: 0; }
.stages .nm { display: block; font-weight: 700; line-height: 1.25; }
.stages .st { display: block; font-size: var(--t-small); color: var(--muted); line-height: 1.3; }
.stages .done .mk { border-color: var(--ok); color: var(--ok); background: var(--tag-ok); }
.stages .next .mk { border-color: var(--accent); color: var(--accent); }
.stages .next .st { color: var(--accent); font-weight: 700; }
.stages [aria-current="step"] { background: var(--accent-soft); box-shadow: inset 0 -3px 0 var(--accent); }
.stages [aria-current="step"] .nm { color: var(--accent-soft-fg); }
.stages .later .cell { color: var(--muted); } .stages .later .mk { border-style: dashed; }
.stages .opt { list-style: none; margin: 0; padding: .6rem 1rem; display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; font-size: var(--t-small); color: var(--muted); border-top: 1px solid var(--line); background: var(--panel-head); border-radius: 0 0 var(--r-card) var(--r-card); }
/* An optional step as a chip: its name, a link, and its state as a word after it. */
.stages .opt li { padding: .25rem .6rem; border: 1px solid var(--line-strong); border-radius: var(--r-button); background: var(--panel); }
.stages .opt a { color: var(--fg); font-weight: 700; text-decoration: none; }
.stages .opt a:is(:hover, :focus-visible) { text-decoration: underline; }
.stages .opt a[aria-current] { color: var(--accent); }
.stages .opt li { flex: none; }
/* A phone: the marks stay a strip, and only the step you are on keeps its words. */
@media (max-width: 40rem) {
  .stages li:not(:has([aria-current])) { flex: none; }
  .stages li:not(:has([aria-current])) .cell { padding: var(--s1); }
  .stages li:not(:has([aria-current])) .txt { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
}
/* The Publish step's check (#175): what each step holds, its Change link at the end of the row;
   a required step not done in the warning color and bold, so not by color alone. */
.dl.summary dd { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0 var(--s3); }
.dl.summary dd.missing > span { color: var(--warn); font-weight: 700; }
/* One step, one column: sections one under another, prose at a readable measure. */
.stage p, .stage .helptext { max-width: 34rem; }  /* about 80 characters of this face */
/* Limits and the like: short numbers under long labels read better one a line (#176). */
.stage .stack select { width: auto; min-width: 10rem; }
.stage-foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s3); margin-top: var(--s4); padding-top: var(--s3); border-top: 1px solid var(--line); }

/* 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; }
/* Each act a row: its name and a line on what it does, and on the right the button that opens
   its confirmation. A phone puts the button under the words. */
.dz-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s1) var(--s3); }
.dz-row > div { flex: 1 1 18rem; }
.dz-row h3 { margin: 0; font-size: var(--t-body); }
.dz-row p { margin: .2rem 0 0; }
.card.dangerzone > :is(.dz-row, dialog) + .dz-row { margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--line); }

/* A destructive act's confirmation, in a dialog over the page (docs/INTERFACE.md): what is lost,
   the reason, and the safe choice before the red one. The button opens it with invoker commands
   (commandfor), so it needs no script; app.js does the same for a browser older than late 2025. */
.confirm-dialog { max-width: min(32rem, 94vw); }
dialog.slot-dialog:modal { margin: auto; }  /* centered, even as a card's last child, whose margin a card zeroes */
.slot-dialog > h2:first-child { margin-top: 0; padding-right: 2rem; }
/* One header and footer for every confirmation (#244): its question as a header band over a rule,
   and its choices as a footer band, the safe one first and the primary at the right. The dialog's
   own padding is 1.25rem 1.5rem 1.5rem; the bands reach its edges. */
.confirm-dialog > h2:first-child { margin: -1.25rem -1.5rem 1rem; padding: 1rem 1.5rem; border-bottom: 1px solid var(--line); }
.confirm-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--s1); margin-top: var(--s2); }
.confirm-dialog .confirm-actions { margin: var(--s3) -1.5rem -1.5rem; padding: .85rem 1.5rem; border-top: 1px solid var(--line); background: var(--panel-head); border-radius: 0 0 var(--r-card) var(--r-card); }
.confirm-dialog { overflow: hidden; }

/* Something more to read, folded: one look everywhere. A chevron in the action color that turns
   as it opens, and what opens set in under a thin rule, so it reads as belonging to its line. */
details:not(.filter) > summary { list-style: none; cursor: pointer; width: fit-content; font-weight: 700; border-radius: var(--r-field); }
details:not(.filter) > summary::-webkit-details-marker { display: none; }
details:not(.filter) > summary::before {
  content: ""; display: inline-block; width: .45em; height: .45em; margin: 0 .6em .15em .15em;
  border-right: 2px solid var(--accent); border-bottom: 2px solid var(--accent); transform: rotate(-45deg);
}
details[open]:not(.filter) > summary::before { transform: rotate(45deg); margin-bottom: .3em; }
details:not(.filter) > summary:hover { color: var(--accent); }
details:not(.filter) > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
details:not(.filter, .inline) { margin-block: var(--s2); }
details[open]:not(.filter, .card, .msglist details) > :not(summary) { margin-left: .35rem; padding-left: .9rem; border-left: 2px solid var(--line); }
/* A whole message or announcement folded into its card: the summary is its heading line. */
details[open]:is(.card, .msglist details) > :not(summary) { margin-left: 1.25rem; }
details:is(.card, .msglist details) > summary { width: auto; }
.msglist details { margin: 0; }
/* 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: var(--t-small); }
.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: center; gap: var(--s1) var(--s3); margin-top: var(--s1); }

/* 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: 400; }
.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: var(--t-small); }
