/*
 * Components (issue #43): small purposeful classes on the token system.
 * If a screen needs a new visual, extend a token or a component here —
 * never inline a one-off value in a view.
 */

/* Site chrome */

header.site {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.nav {
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-3) var(--space-4);
  display: flex;
  flex-wrap: wrap;
  /* A column gap of space-3 since the mark joined the name (issue #281):
     the menu keeps its one line at the measure's width, where space-4
     broke "This node" in two. A narrower window wraps whole links. */
  gap: var(--space-2) var(--space-3);
  /* Centred, not baseline: the brand link is as tall as its 32 px mark,
     and a baseline row put the name's text 3.6 px below the menu's. Every
     item centred on the row puts text of one size on one line. */
  align-items: center;
}

/* The bar speaks in the headings face, not the reading one (pass 1).
   Two reasons, and the second is measurable. Navigation is chrome: a
   label on a door, not a sentence, and the reading serif made the bar
   read like the first line of the page. And with the wordmark in one face
   and the links in another, `align-items: center` centred *boxes* of
   different heights — the brand's text box measured 19px against the
   links' 24px, because Literata's line box is taller — which put the
   baselines 2px apart. One face for the bar aligns them by construction
   rather than by a nudge that the next size change would break. */
.nav {
  font-family: var(--font-headings);
}

.nav a {
  color: var(--accent);
  text-decoration: none;
  white-space: nowrap;
}

/* The footer's chrome (issue #286): what is not a member's daily work, one
   click from every screen. It reads as secondary to the bar — a hairline
   above, smaller type, no surface of its own — but its links keep
   `.nav a`'s colour and the global `a:focus-visible` ring, because a
   footer whose entries are harder to see or to reach is the same as
   deleting them. `main`'s own bottom padding is the space above it.

   The column below is what keeps it at the bottom on a short page — an
   empty listing is the commonest screen on a new relay, and a hairline
   floating halfway up the window reads as a mistake rather than as the
   end of the page. `main` grows; the two bars keep their own height. */
body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

main {
  flex: 1;
}

.site-footer {
  border-top: 1px solid var(--line);
}

.nav--footer {
  font-size: var(--text-sm);
}

.nav a:hover {
  text-decoration: underline;
}

.nav a.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--ink);
  /* The wordmark takes the headings face, not the reading one (pass 1):
     it stands beside the pixel mark as a logotype, and a name set in the
     body serif reads as the first word of a sentence. The only component
     line pass 1 touches, and it is here because leaving it would ship a
     visible mistake for the sake of a layer boundary. */
  font-family: var(--font-headings);
  font-weight: var(--weight-headings);
  /* A logotype's breathing room (Ben's pick, 2026-09-13): the same face
     as the bar, tracked a hair so the name reads as a mark rather than as
     the first of eight labels. Tracking only — the face is the bar's, so
     the baselines stay aligned by construction. */
  letter-spacing: 0.01em;
}

/* The mark (issue #281, docs/brand.md), drawn by lib/brand.rb: the relays
   in ink, the signal in the mark's deep amber. As a page loads the
   ricochet plays once, one step every 110 ms: the left relay flashes, the
   signal climbs, the top relay flashes, the signal drops, the right relay
   flashes. It then rests on the still frame, and never loops in a working
   tool. With reduced motion, only the still frame shows. */
.brand-mark {
  flex-shrink: 0;
}

.brand-mark__relay {
  fill: var(--ink);
}

.brand-mark__signal {
  fill: var(--brand-amber-deep);
  opacity: 0.25;
}

.brand-mark__beam,
.brand-mark__flash {
  fill: var(--brand-amber-deep);
  animation-fill-mode: both;
  animation-timing-function: step-end;
}

.brand-mark__beam {
  animation-name: brand-mark-beam;
  animation-duration: 10ms;
}

.brand-mark__flash {
  animation-name: brand-mark-flash;
  animation-duration: 180ms;
}

.brand-mark__step--0 { animation-delay: 0ms; }
.brand-mark__step--1 { animation-delay: 110ms; }
.brand-mark__step--2 { animation-delay: 220ms; }
.brand-mark__step--3 { animation-delay: 330ms; }
.brand-mark__step--4 { animation-delay: 440ms; }
.brand-mark__step--5 { animation-delay: 550ms; }
.brand-mark__step--6 { animation-delay: 660ms; }
.brand-mark__step--7 { animation-delay: 770ms; }
.brand-mark__step--8 { animation-delay: 880ms; }
.brand-mark__step--9 { animation-delay: 990ms; }
.brand-mark__step--10 { animation-delay: 1100ms; }

@keyframes brand-mark-beam {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes brand-mark-flash {
  0% { opacity: 0; }
  1% { opacity: 0.9; }
  100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .brand-mark__beam {
    animation: none;
    opacity: 1;
  }

  .brand-mark__flash {
    animation: none;
    opacity: 0;
  }
}

/* `width: 100%` is load-bearing, not belt and braces. `body` is a column
   flex container (it is what keeps the footer at the bottom of a short
   page), and an auto margin on a flex item overrides the default stretch
   — so without a definite width `main` is shrink-to-fit and **its width
   depends on its content**. Opening a step on a record, or the filters on
   a listing, brought in something wider, `main` grew, and `margin: 0 auto`
   re-centred it: the whole page jumped 22 px sideways on a click. Latent
   since the footer landed (issue #286); the folds are what made it show. */
main {
  width: 100%;
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-5) var(--space-4) var(--space-8);
}

/* Buttons: .btn is the primary action; variants for the rest.
   A button is a **label on an act**, not a sentence, so it takes the
   headings face like the bar does (pass 2) — and the acts on these
   screens are consequential enough to be read as labels: "Create needs",
   "This need is met", "Reveal key". */

.btn {
  display: inline-block;
  font-family: var(--font-headings);
  font-size: var(--text-sm);
  font-weight: var(--weight-headings);
  letter-spacing: 0.01em;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
  text-decoration: none;
}

/* Darker under the pointer, never brighter: a warm amber lightens chalky,
   which `brightness(1.08)` proved. */
.btn:hover {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
}

.btn--secondary {
  background: transparent;
  color: var(--accent);
}

.btn--secondary:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.btn--danger {
  background: transparent;
  border-color: var(--danger);
  color: var(--danger);
}

.btn--danger:hover {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--accent-ink);
}

/* Cards */

/* A card is a panel on paper, not a chip floating above it (pass 2): the
   lift came out and a hairline stayed. On a warm ground a shadow reads as
   a UI trope — "beauty as a choice, never bling" (doctrine §5) — and the
   screens that matter here are records, which lie flat. `--shadow-sm`
   survives as a token for anything that genuinely floats. */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  margin: var(--space-4) 0;
}

/* Suggested needs on a trial relay (issue #267): three rows, numbered
   because the order is the lesson — each one reaches further than the one
   above it. The chip is the action and the line under it says what the
   suggestion teaches, so the two never read as one sentence. */

.suggestions {
  margin: var(--space-3) 0 0;
  padding-left: var(--space-5);
}

.suggestions li + li {
  margin-top: var(--space-3);
}

.suggestions li::marker {
  color: var(--muted);
  font-size: var(--text-sm);
}

.suggestions__chip {
  display: inline-block;
  font-weight: var(--weight-semibold);
}

.suggestions .hint {
  display: block;
  font-size: var(--text-sm);
}

/* What crossed, and what did not (issue #275). Two treatments, because
   the two sides sit differently on the page: the send is a page of its
   own, and the arrival's complement is a fold-out in the flow under the
   projection it qualifies — where a fold-out works, unlike the table cell
   it started in. Both keep the global `summary:focus-visible` ring. */

.disclosure {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
}

.disclosure > summary {
  color: var(--accent);
}

.disclosure ul {
  margin: var(--space-2) 0 0;
}

/* Each fact's "why" reads under it, never beside it: inline, the reason
   ran into the value and the two became one sentence. */
.disclosure .hint,
.wire-facts dd .hint,
.wire-facts li .hint {
  display: block;
}

.wire-facts li + li {
  margin-top: var(--space-2);
}

/* Tables: the bordered listing treatment */

/* A table wider than the measure scrolls, it does not vanish (README
   *Design pass*). `.table`'s own `overflow: hidden` rounds its corners and
   would otherwise **clip** the columns that do not fit — which on the
   personas table means the row's actions, so an administrator on a
   narrower window loses the button rather than the border of a cell.
   Found when Ben could not see "Reveal key" (issue #271). */
.table-scroll {
  overflow-x: auto;
}

/* A table is a table in a record, not a boxed widget (pass 2): the outer
   border and the rounded corners came off, a rule sits under the header
   and a hairline under each row. What a reader follows across a line is
   the row, and a box around all of them adds nothing to that.

   **Figures are tabular.** Literata's default figures are proportional,
   so "5 seat(s)" and "12 seat(s)" set to different widths and a column of
   quantities, dates or coverage counts fails to line up — which is the one
   thing a column of numbers is for. */
.table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.table th,
.table td {
  padding: var(--space-2) var(--space-4) var(--space-2) 0;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}

.table th {
  color: var(--muted);
  font-family: var(--font-headings);
  font-size: var(--text-xs);
  font-weight: var(--weight-headings);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--ink);
  white-space: nowrap;
}

.table tbody tr:last-child td {
  border-bottom: none;
}

.table td.content p {
  margin: 0;
}

/* Status badges: one variant per lifecycle meaning (see
   ApplicationHelper::STATUS_VARIANT — the single mapping, tested) */

/* A status is a **stamp on a record**, not a chip in an app (pass 2): the
   pill became a small rectangle in the headings face, tracked, at the
   smallest size in the scale. Deliberately not uppercased — the same
   class carries the protocol's own words on This relay
   (`accept_need_forward_relayable`), and shouting those would make the
   longest labels the loudest thing on the page. The colour mapping is
   untouched: it is one tested table (ApplicationHelper::STATUS_VARIANT). */
.badge {
  display: inline-block;
  padding: 0.1rem var(--space-2);
  border-radius: var(--radius-sm);
  font-family: var(--font-headings);
  font-size: var(--text-xs);
  font-weight: var(--weight-headings);
  letter-spacing: 0.03em;
  background: var(--neutral-bg);
  color: var(--muted);
}

.badge--info {
  background: var(--info-bg);
  color: var(--info);
}

.badge--success {
  background: var(--success-bg);
  color: var(--success);
}

.badge--warning {
  background: var(--warning-bg);
  color: var(--warning);
}

.badge--danger {
  background: var(--danger-bg);
  color: var(--danger);
}

/* Form fields: label above control, consistent rhythm */

.form-field {
  margin: 0 0 var(--space-4);
}

/* A label names a field; it is not part of the sentence the field is in
   (pass 2). In the headings face, small and tracked, it reads as the
   caption on a form rather than as bold prose — which is what the reading
   serif at 600 had made of it. The same hand as the table headers, the
   badges and the buttons: everything that labels rather than speaks. */
/* `dt` is styled in base.css as an element default; its *hand* belongs
   here with every other label, because the decision is "what labels look
   like on these screens" and not "what a definition term is". The detail
   lists on the need, the offer, the entry and the engagement are the most
   label-dense thing we have. */
.form-field label,
.field__label,
.filters label,
.page-head__kind,
.record__facts-title,
.complete > summary,
.filter-fold > summary,
.act > summary,
dl dt {
  display: block;
  margin-bottom: var(--space-1);
  color: var(--muted);
  font-family: var(--font-headings);
  font-size: var(--text-xs);
  font-weight: var(--weight-headings);
  letter-spacing: 0.03em;
}

/* A hint belongs under its field, not beside it. `span.hint` is inline,
   so wherever a field was narrower than its column — a door panel, a
   margin — the sentence explaining it wrapped around the input and read
   as a caption to nothing (found on the claim screen, 2026-09-13). */
.form-field .hint {
  display: block;
  margin-top: var(--space-1);
}

.form-field input,
.form-field select,
.form-field textarea {
  min-width: 0;
  width: 100%;
  max-width: 28rem;
}

/* Flashes */

/* A flash answers the person's own act and then goes (doctrine R9: never
   a stimulus). Shaped like the posture notes further down the page — a
   rule on the left carrying the meaning, a tint behind the words — so the
   screens have one way of saying "read this", not two (pass 2). */
.flash {
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid;
}

.flash--notice {
  background: var(--success-bg);
  border-left-color: var(--success);
  color: var(--success);
}

.flash--alert {
  background: var(--danger-bg);
  border-left-color: var(--danger);
  color: var(--danger);
}

/* Empty states (listings with nothing to show) */

/* "Nothing here yet" is a statement about the record, not a placeholder
   waiting for a drop (pass 2): the dashed outline read as a drop target
   and centred text read as an illustration's caption. A quiet panel, left
   aligned like everything else that speaks. */
.empty-state {
  padding: var(--space-4) var(--space-5);
  background: var(--neutral-bg);
  border-radius: var(--radius-md);
  color: var(--muted);
}

/* Filter forms (issue #42): one compact row of labeled controls */

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-4);
  align-items: flex-end;
  margin-bottom: var(--space-5);
}

.filters p {
  margin: 0;
}

/* The typography is the shared label hand above; this rule kept its own
   copy of it and, sitting later in the file, quietly won — which is why
   the filter row was the one place still speaking in the old voice
   (caught in the after-capture, pass 2). Nothing of its own is left. */

.filters input[type="text"],
.filters select {
  min-width: 11rem;
}

.filters .actions {
  align-items: center;
}

/* A yes/no filter (issue #187): the box and its label on one line, level
   with the row's controls rather than stacked like a labelled field. */

.filters__check {
  padding-bottom: var(--space-2);
}

.filters__check label {
  display: inline;
  margin-left: var(--space-1);
}

.filters .actions a {
  color: var(--muted);
  font-size: var(--text-sm);
}

/* Action rows (decision buttons, lifecycle transitions) */

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.actions form {
  display: inline;
}

/* Triage needs rows (issue #41) */

.need-row {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin: 0 0 var(--space-4);
}

/* Engagement show */

/* A value meant to be carried somewhere else by hand — a handoff link
   (continue_url, Bible §12.2), a 52-character public key (issue #116).
   Transcription is the failure mode, so the value is always selectable and
   always has its own copy button. */

.copy-field {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}

.copy-field input {
  flex: 1;
  /* The token, not a stack of its own (pass 2): a key is raw, and what is
     raw is set in one face across every screen. */
  font-family: var(--font-mono);
  font-size: var(--text-sm);
}

/* A labelled value: the label above, the value below, stacked so a long
   key never fights its label for the same line. */

.field {
  margin: var(--space-3) 0;
}

/* The typography is shared with every other label above; what is this
   one's own is the space it keeps around a value that may be 52
   characters of key. */
.field__label {
  margin-bottom: var(--space-1);
}

.field p,
dl dd {
  margin: 0;
  /* Quantities, windows and coverage counts live in these values, and a
     column of figures that does not line up is a column of figures that
     was not worth putting in a column. */
  font-variant-numeric: tabular-nums;
}

/* Posture notes (issue #116): one per fact that is otherwise implicit.
   The left rule carries the severity, so the note reads as a statement
   about the node rather than as an alert about the page. */

.posture {
  border-left: 3px solid var(--line);
  padding: var(--space-2) 0 var(--space-2) var(--space-3);
  margin: var(--space-3) 0;
}

.posture--danger {
  border-left-color: var(--danger-line);
}

.posture--warning {
  border-left-color: var(--warning-line);
}

.posture--info {
  border-left-color: var(--info-line);
}

.posture__headline {
  margin: 0 0 var(--space-1);
  font-weight: var(--weight-semibold);
}

.table caption {
  caption-side: top;
  text-align: left;
  color: var(--muted);
  font-size: var(--text-sm);
  padding: var(--space-2) var(--space-3);
}

/* The action plan (issue #185): the three steps a need or an offer takes
   to reach someone, as a numbered list with the state badge in front of
   each — the table of contents of the page, with its state filled in. */

.plan {
  list-style: none;
  counter-reset: plan-step;
  margin: var(--space-4) 0 var(--space-5);
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

.plan__step {
  counter-increment: plan-step;
  display: grid;
  grid-template-columns: 1.5rem 5rem 1fr;
  gap: var(--space-2) var(--space-3);
  align-items: baseline;
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--line);
}

.plan__step:last-child {
  border-bottom: none;
}

.plan__step::before {
  content: counter(plan-step) ".";
  color: var(--muted);
  font-weight: var(--weight-semibold);
}

.plan__step .badge {
  grid-column: 2;
  grid-row: 1;
}

.plan__step strong {
  grid-column: 3;
  grid-row: 1;
}

.plan__detail {
  grid-column: 3;
  color: var(--muted);
  font-size: var(--text-sm);
}

/* The ask form (issue #185): the community's contacts as a pick list,
   one column per organization, the organization's name as its heading. */

.pick {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-6);
  margin-bottom: var(--space-4);
}

.pick__group {
  min-width: 12rem;
}

.pick__group h3 {
  margin: 0 0 var(--space-2);
  color: var(--muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.pick__group p {
  margin: 0 0 var(--space-1);
}

.pick__group label {
  margin-left: var(--space-1);
}

/* The record screen, arranged (issue #298, pass 3 of docs/design.md §4;
   the board is docs/design-board-pass3b.html, reading C2).

   A record page is two things at once — what is true about the record,
   and what can be done about it — and it used to stack them in one
   column of same-sized headings that said nothing about which was which.
   Here the spine is what happens, in the order the plan computes, and
   the margin is what is true. Neither column repeats the other. */

.page-head {
  border-bottom: 1px solid var(--line);
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-5);
}

.page-head__line {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.page-head__line h1 {
  margin: 0;
}

.page-head__subject {
  margin: var(--space-1) 0 0;
  color: var(--muted);
}

.record {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 17rem;
  gap: var(--space-6);
  align-items: start;
}

/* The margin is a column of facts, not a two-column grid inside a
   two-column grid: `dl` is a grid by default in base.css, and at 17rem
   its 9rem term column leaves nothing for the value. */
/* Both columns may shrink below their content. A grid item defaults to
   `min-width: auto`, which means *min-content*, so `minmax(0, 1fr)` on
   the track is not enough on its own: the work column kept the width of
   the longest thing in it — a site key, a digest — and ran across the
   margin (found on the triage entry, which carries all three). */
.record__work,
.record__facts {
  min-width: 0;
}

.record__facts {
  border-left: 1px solid var(--line);
  padding-left: var(--space-4);
}

.record__facts dl {
  display: block;
  margin: 0;
}

.record__facts dt {
  margin-top: var(--space-3);
}

.record__facts dd {
  margin: 0;
}

.record__elsewhere {
  margin-top: var(--space-5);
}

@media (max-width: 62rem) {
  .record {
    grid-template-columns: 1fr;
  }

  .record__facts {
    border-left: 0;
    padding-left: 0;
    border-top: 1px solid var(--line);
    padding-top: var(--space-4);
  }
}

/* The plan as the page. The same ordered list as the summary card, with
   the card's own furniture taken off: each step is a `<details>` whose
   summary is its heading, so the list is a table of contents when it is
   closed and the sections themselves when it is open. */

.plan--spine {
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
}

.plan--spine .plan__step {
  display: block;
  padding: 0;
  border-bottom: 0;
  border-top: 1px solid var(--line);
}

.plan--spine .plan__step:last-child {
  border-bottom: 1px solid var(--line);
}

/* The number moves into the summary, where it can sit beside the title;
   the card's counter would print a second one. */
.plan--spine .plan__step::before {
  content: none;
}

/* A grid rather than a flex row, so the four parts land in the same
   place on every step: number, title and state on the first line, the
   plan's sentence on the second, indented under the title. Wrapped by
   width instead, a long note jumped to its own line on some steps and
   not others, and three steps read as three different shapes. */
.step > summary {
  display: grid;
  grid-template-columns: 1.5rem auto 1fr;
  gap: var(--space-1) var(--space-3);
  align-items: baseline;
  padding: var(--space-3) var(--space-5) var(--space-3) 0;
  position: relative;
  cursor: pointer;
  list-style: none;
}

.step > summary > .badge {
  grid-column: 3;
  justify-self: start;
}

.step > summary::-webkit-details-marker {
  display: none;
}

/* Our own marker, because the native one differs by browser and sits
   where the number belongs. It turns, so the closed and open states are
   the same shape rather than two symbols to learn. */
.step > summary::after {
  content: "";
  position: absolute;
  right: var(--space-2);
  top: 1.15em;
  width: 0.42rem;
  height: 0.42rem;
  border-right: 1.5px solid var(--muted);
  border-bottom: 1.5px solid var(--muted);
  transform: rotate(-45deg);
}

.step[open] > summary::after {
  transform: rotate(45deg);
}

.step > summary:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.step__n {
  color: var(--muted);
  font-family: var(--font-headings);
  font-weight: var(--weight-headings);
}

.step__title {
  font-family: var(--font-headings);
  font-weight: var(--weight-headings);
}

.step__note {
  grid-column: 2 / -1;
  color: var(--muted);
  font-size: var(--text-sm);
}

.step__body {
  padding: 0 0 var(--space-5) calc(1.5rem + var(--space-3));
}

.step__body > :first-child {
  margin-top: 0;
}

/* Completing the record stands with the facts, because that is what it
   edits — folded, because a form is not what a reader came for. */
.complete {
  margin-top: var(--space-4);
}

.complete > summary {
  cursor: pointer;
  margin-bottom: 0;
}

.complete > summary:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.complete input,
.complete select {
  min-width: 0;
  width: 100%;
}

/* A listing's filters, folded (issue #298). The rows are what the screen
   is for, so the six controls sit behind one line instead of standing
   between the title and the first row — on a seeded relay the form was
   taller than the table it filters.

   A narrowed listing opens itself (the partial decides), so this needs
   no open state of its own beyond the marker turning. */

.filter-fold {
  margin: 0 0 var(--space-4);
}

.filter-fold > summary {
  position: relative;
  padding: var(--space-2) var(--space-5) var(--space-2) 0;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  list-style: none;
  margin-bottom: 0;
}

.filter-fold > summary::-webkit-details-marker {
  display: none;
}

/* The same turning marker as a record's steps: one disclosure gesture on
   these screens, not two. */
.filter-fold > summary::after {
  content: "";
  position: absolute;
  right: var(--space-2);
  top: 1em;
  width: 0.42rem;
  height: 0.42rem;
  border-right: 1.5px solid var(--muted);
  border-bottom: 1.5px solid var(--muted);
  transform: rotate(-45deg);
}

.filter-fold[open] > summary::after {
  transform: rotate(45deg);
}

.filter-fold > summary:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* What is on, in the accent: a closed fold that is hiding rows has to
   say so louder than its own name. */
.filter-fold__on {
  color: var(--accent);
  letter-spacing: 0.03em;
}

.filter-fold .filters {
  margin-top: var(--space-3);
}

/* The triage entry (issue #298): an arrival, the decision it asks for,
   and the acts that follow from it. */

/* What a record is asking of the person reading it: the triage
   decision, and the acts that move an engagement along. Both were a bare
   row of buttons between two sections, and on a long page the one thing
   the screen exists for was the hardest to find.

   The posture note's shape, because all three mean "this is what is
   being asked of you" and the screens should have one way of saying it. */
.decide {
  border-left: 3px solid var(--accent);
  background: var(--surface);
  padding: var(--space-3) var(--space-4);
  margin: var(--space-5) 0;
}

.decide > h2 {
  margin: 0 0 var(--space-3);
  font-size: var(--text-lg);
}

.decide > h3 {
  font-size: 1rem;
  margin: var(--space-4) 0 var(--space-2);
}

/* One act a reader can take, folded. Same disclosure as a record's plan
   steps and a listing's filters. */
.act {
  border-top: 1px solid var(--line);
}

.act:last-of-type {
  border-bottom: 1px solid var(--line);
}

.act > summary {
  position: relative;
  padding: var(--space-3) var(--space-5) var(--space-3) 0;
  cursor: pointer;
  list-style: none;
  margin-bottom: 0;
  font-size: var(--text-sm);
}

.act > summary::-webkit-details-marker {
  display: none;
}

.act > summary::after {
  content: "";
  position: absolute;
  right: var(--space-2);
  top: 1.15em;
  width: 0.42rem;
  height: 0.42rem;
  border-right: 1.5px solid var(--muted);
  border-bottom: 1.5px solid var(--muted);
  transform: rotate(-45deg);
}

.act[open] > summary::after {
  transform: rotate(45deg);
}

.act > summary:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.act__body {
  padding: 0 0 var(--space-5);
}

.act__body > :first-child {
  margin-top: 0;
}

/* Inside an act, a heading is a sub-heading: the summary above it is
   already the section's name. */
.act__body h2,
.act__body h3 {
  font-family: var(--font-headings);
  font-size: 1rem;
  margin: var(--space-4) 0 var(--space-2);
}

/* One section of a record, folded (issue #298). The organization is six
   of these — who is in it, what it offers, needs and has agreed, who
   acts for it, who it trusts — and a reader arrives wanting one. Closed,
   the six are a table of contents; opened, the one they came for is the
   page.

   The heading keeps its own type: `<summary>` may hold an `h1`–`h6`, so
   the outline survives the fold and `h2` still looks like `h2`. */

.record-section {
  border-top: 1px solid var(--line);
}

.record-section:last-of-type {
  border-bottom: 1px solid var(--line);
}

.record-section > summary {
  position: relative;
  padding: var(--space-2) var(--space-5) var(--space-2) 0;
  cursor: pointer;
  list-style: none;
}

.record-section > summary::-webkit-details-marker {
  display: none;
}

.record-section > summary > h2 {
  display: inline;
  margin: 0;
}

/* How many things are inside, so a closed section can be read without
   being opened. A bare number in the labelling hand: the heading beside
   it already says what is counted. */
/* How many rows a listing is showing, beside its title — the same bare
   number, in the same hand, as a folded section's count. One way of
   saying "how many" on these screens. */
.listing-count,
.record-section__count {
  margin-left: var(--space-2);
  font-weight: normal;
  color: var(--muted);
  font-family: var(--font-headings);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

/* The same turning marker as every other disclosure on these screens. */
.record-section > summary::after {
  content: "";
  position: absolute;
  right: var(--space-2);
  top: 1.15em;
  width: 0.42rem;
  height: 0.42rem;
  border-right: 1.5px solid var(--muted);
  border-bottom: 1.5px solid var(--muted);
  transform: rotate(-45deg);
}

.record-section[open] > summary::after {
  transform: rotate(45deg);
}

.record-section > summary:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.record-section__body {
  padding: 0 0 var(--space-5);
}

.record-section__body > :first-child {
  margin-top: 0;
}

/* An act inside a section is one level in: it loses the rules that make a
   top-level fold read as a band of its own, and keeps only its own line. */
.record-section__body .act {
  border-bottom: 0;
}

.record-section__body .act:last-of-type {
  border-bottom: 1px solid var(--line);
}

/* The door (issue #298): sign in, ask for a reset, set a password. The
   first screen anyone sees of a relay, and it was a form at the top-left
   of an 864px column — a page of work with nothing on it.

   A narrow centred panel instead, because a door has one thing to do and
   should look like one thing. The head keeps no rule under it: there is
   nothing below to separate it from. */

.door {
  max-width: 22rem;
  margin: var(--space-8) auto;
}

.door .page-head {
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: var(--space-5);
}

.door input,
.door .btn {
  min-width: 0;
  width: 100%;
}

.door .btn {
  margin-top: var(--space-2);
  text-align: center;
}

.door > p:last-child {
  margin-top: var(--space-5);
  font-size: var(--text-sm);
}

/* Claiming a relay is a door screen with more to say: one task, and
   hints that are prose rather than labels. */
.door--wide {
  max-width: 34rem;
}

/* The recovery phrase (issue #298). Twelve words shown once and never
   again, and they were an input like any other — the same treatment as
   an endpoint URL on This relay.

   A panel of their own, in the danger colour that every other "you
   cannot undo this" carries, with the warning inside it rather than
   under it: a reader who copies and scrolls past must not be able to
   take the words without the sentence that says they will not come
   back. */
.phrase {
  border: 1px solid var(--danger);
  border-left-width: 3px;
  background: var(--surface);
  padding: var(--space-4);
  margin: var(--space-4) 0;
}

/* Read at the size words are read at, not at the size a form field is
   filled in at: these are meant to be copied onto paper. */
.copy-field--phrase textarea {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  line-height: 1.6;
  resize: none;
  overflow: hidden;
}

.phrase__once {
  margin: var(--space-3) 0 0;
  color: var(--danger);
}

/* The reach map (issue #274): this relay, the communities it knows, and a
   fainter arc behind each for the count that community publishes about
   itself. Server-rendered SVG, so everything here is colour and weight —
   the geometry is ReachMap's.

   The states carry the lifecycle colours the badges already use, so green
   means the same thing on this screen as on every other one. */

.reach-map {
  max-width: 34rem;
  margin: var(--space-5) auto;
}

.reach-map__svg {
  width: 100%;
  height: auto;
  overflow: visible;
}

/* `--line` is a hairline for a box edge and vanishes as a thin stroke on
   paper: the pinned spoke was drawn and invisible on the first render.
   The muted ink at low opacity is faint by intent rather than by
   accident, and dashed, because this relay has taken no decision on it. */
.reach-map__spoke {
  stroke: var(--muted);
  stroke-width: 1.5;
  stroke-dasharray: 3 4;
  opacity: 0.5;
}

/* A spoke this relay decided on is solid; one it never took, or has
   stepped out of, stays dashed — the row stays after a revocation and the
   picture should say so rather than quietly close the gap. */
/* A spoke this relay decided on is solid and at full strength; one it
   never took, or has stepped out of, stays dashed and faint — the row
   stays after a revocation, and the picture should say so rather than
   quietly close the gap. */
.reach-map__spoke--mutual,
.reach-map__spoke--ours {
  stroke-dasharray: none;
  opacity: 1;
}

/* The second channel: what has actually crossed. Trust is a decision and
   lives in the colour and the dash; traffic is a fact and lives in the
   weight, so a peer trusted last month and never heard from is visibly
   not the one something crosses with weekly. Three bands — a reader is
   asking which of these are alive, and the list carries the number. */
.reach-map__spoke--crossed-some {
  stroke-width: 2.5;
}

.reach-map__spoke--crossed-busy {
  stroke-width: 4;
}

.reach-map__centre {
  fill: var(--accent);
}

/* `paint-order` puts the stroke behind the fill, which turns a stroke the
   colour of the paper into a halo: a label stays readable wherever a spoke
   or an arc runs under it, with no library and no re-layout. */
.reach-map__centre-label,
.reach-map__label,
.reach-map__key {
  paint-order: stroke;
  stroke: var(--paper);
  stroke-width: 4px;
  stroke-linejoin: round;
}

.reach-map__centre-label {
  fill: var(--ink);
  font-family: var(--font-headings);
  font-weight: var(--weight-headings);
  font-size: 14px;
}

.reach-map__peer {
  fill: var(--surface);
  stroke: var(--muted);
  stroke-width: 1.5;
}

/* The ring says the peer answered the question at all. A published zero
   and a published nothing are the same size — there is nowhere smaller to
   go — so this is what separates them. Faint, because it is the peer's
   word and not a decision of ours. */
.reach-map__says {
  fill: none;
  stroke: var(--muted);
  stroke-width: 1;
  opacity: 0.4;
}

/* Trusted here and answering: filled, in the colour every other screen
   uses for a thing that has happened. */
.reach-map__peer--mutual {
  fill: var(--success);
  stroke: var(--success);
}

.reach-map__peer--ours {
  fill: var(--info);
  stroke: var(--info);
}

.reach-map__peer--theirs,
.reach-map__peer--revoked {
  fill: var(--paper);
  stroke: var(--muted);
  stroke-dasharray: 2 3;
}

.reach-map__label {
  fill: var(--ink);
  font-family: var(--font-headings);
  font-size: 12px;
}

/* The public identity, hinted. In the raw face, because that is what a
   key is on every other screen — and quieter than the name, because the
   name is what a reader is looking for and the key is what tells two of
   them apart when the names do not. */
.reach-map__key {
  fill: var(--muted);
  font-family: var(--font-mono);
  font-size: 10px;
}

.reach-map a:hover .reach-map__label,
.reach-map a:focus-visible .reach-map__label {
  fill: var(--accent);
}

.reach-map a:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
}

/* Prose at a reading width on a screen that has no margin to hold it. */
.measure {
  max-width: 38rem;
}

/* The key to the map (issue #274). Three encodings, shown rather than
   named: a reader who has to guess what a bigger circle means is reading
   a puzzle. Above the list, not beside the picture — the picture is
   already carrying three channels and does not need a fourth thing in
   its margin. */

.reach-key {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  margin: 0 0 var(--space-4);
  padding: 0;
  list-style: none;
  color: var(--muted);
  font-size: var(--text-sm);
}

.reach-key li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.reach-key svg {
  width: 44px;
  height: 20px;
  flex: none;
}

.reach-key__peer {
  fill: var(--surface);
  stroke: var(--muted);
  stroke-width: 1.5;
}

/* The two ends of the trust reading, in the colours the map uses. Their
   own classes rather than the map's: the key is a component, and reaching
   into another one's modifiers is how a sample quietly stops matching the
   thing it is a sample of — which it did, on the first render, because
   the key's own fill won the cascade. */
.reach-key__mutual {
  fill: var(--success);
  stroke: var(--success);
  stroke-width: 1.5;
}

.reach-key__pinned {
  fill: var(--paper);
  stroke: var(--muted);
  stroke-width: 1.5;
  stroke-dasharray: 2 3;
}

.reach-key__thin,
.reach-key__thick {
  stroke: var(--muted);
}

.reach-key__thin {
  stroke-width: 1.5;
}

.reach-key__thick {
  stroke-width: 4;
}

/* A peer's profile, in words (issue #274). A native `popover`: an
   element, not a widget — dismissed by Escape and by clicking away with
   no line of ours, and the button needs no controller.

   Where the attribute is unsupported the card is a plain block inside
   its cell. Ugly, and the content is never lost: the `@supports` below
   makes that a decision rather than an accident. */

.profile-card {
  max-width: 26rem;
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-md);
}

.profile-card:popover-open {
  /* Centred on the viewport rather than anchored to the button: anchor
     positioning is not everywhere yet, and a card that appears in the
     middle is never the card that appears off-screen. */
  margin: auto;
}

.profile-card h3 {
  margin: 0 0 var(--space-3);
}

.profile-card dl {
  grid-template-columns: 8rem 1fr;
}

.profile-card .actions {
  margin-top: var(--space-4);
}

.profile-card__kind {
  margin: 0 0 var(--space-1);
  color: var(--muted);
  font-family: var(--font-headings);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* The hit area over a peer's dot: an HTML button in a `<foreignObject>`,
   because `popovertarget` is only valid on a button and SVG has none. The
   circle underneath stays SVG — that is what keeps it a picture — and
   this is only the target. */
.reach-map__hit {
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
}

.reach-map__hit:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
}

@supports not selector(:popover-open) {
  .profile-card {
    max-width: none;
    margin-top: var(--space-3);
    box-shadow: none;
  }

  .reach-map__hit {
    cursor: default;
  }
}

/* A link out of a record, standing on its own under the sections: the
   federation directory from an organization. Not a folded section — a
   section folds because there is something behind it worth a click, and
   a fold over one link asks a reader to click to reveal a click. */
.record-link {
  margin-top: var(--space-5);
}
