/*
  porch, by denyfirst
  -------------------
  A dark, quiet surface with one red accent, and the same page in light for
  whoever prefers it. The colour scheme follows the system until the reader
  picks one with the switch in the header; theme.js remembers that choice and
  nothing else.

  What could not be measured is set in the same weight as what could, because a
  reader who is not told what was skipped will read silence as a clean result.
  Every colour text is set in is checked for contrast against both surfaces in
  both schemes (contrast_test.go), so a token below is a promise, not a taste.
*/

:root {
  --paper:       #f7f7f8;
  --paper-sunk:  #ececf0;
  --ink:         #16171b;
  --ink-soft:    #464953;
  /* The faint ink carries the coverage line, the words "not measured", and
     everything else a reader has to see in order not to mistake silence for a
     clean result. It is held to the same threshold as the body text. */
  --ink-faint:   #5c606a;
  --rule:        #d9d9df;

  /* Links. The accent, deepened for a light page. */
  --link:        #a41d2a;

  /* The brand red of the wordmark. */
  --brand:       #c42b3a;

  /* A button's face. Its label is white in both schemes. */
  --accent:       #b3202e;
  --accent-hover: #8f1a25;

  --strong:      #1f6a44;
  --weak:        #845900;
  --insecure:    #b3202e;

  /* The terminal the install commands are drawn in. Dark in both schemes, so
     the dark block does not restate them, and each text colour is checked
     against the panel and its bar rather than the paper. */
  --term-bg:     #0e1013;
  --term-bar:    #1a1d22;
  --term-rule:   #2b2f36;
  --term-ink:    #e8e9ec;
  --term-faint:  #959aa5;
  --term-prompt: #ff7c86;
  --term-cmd:    #7cc4ff;
  --term-flag:   #c9a7ff;
  --term-str:    #a6dc85;
  --term-var:    #f2c97d;

  --sans: "Segoe UI Variable Text", "Segoe UI", ui-sans-serif, system-ui,
          -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "Cascadia Mono", "SF Mono", "Roboto Mono", Menlo,
          Consolas, monospace;

  --edge: rgba(22, 23, 27, 0.20);
  --radius: 6px;

  --measure: 38rem;
  --page: 78rem;

  color-scheme: light;
}

/* The design's own scheme: the system's choice, unless the switch said light. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:      #101114;
    --paper-sunk: #18191d;
    --ink:        #f0f0f2;
    --ink-soft:   #b4b5be;
    --ink-faint:  #999ba6;
    --rule:       #2c2d33;
    --link:       #f28b94;
    --brand:      #ed4552;
    --accent:       #c72f3d;
    --accent-hover: #d23443;
    --strong:     #6fbf92;
    --weak:       #d9a23c;
    --insecure:   #ff7c86;
    --edge:       rgba(240, 240, 242, 0.18);
    color-scheme: dark;
  }
}

/* And the switch, when it said dark on a light system. */
:root[data-theme="dark"] {
  --paper:      #101114;
  --paper-sunk: #18191d;
  --ink:        #f0f0f2;
  --ink-soft:   #b4b5be;
  --ink-faint:  #999ba6;
  --rule:       #2c2d33;
  --link:       #f28b94;
  --brand:      #ed4552;
  --accent:       #c72f3d;
  --accent-hover: #d23443;
  --strong:     #6fbf92;
  --weak:       #d9a23c;
  --insecure:   #ff7c86;
  --edge:       rgba(240, 240, 242, 0.18);
  color-scheme: dark;
}

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

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  line-height: 1.6;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--link);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

a:hover { text-decoration-thickness: 2px; }

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

/* ── Masthead ─────────────────────────────────────────────────────────── */

.masthead {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  flex-wrap: wrap;
  max-width: var(--page);
  margin: 0 auto;
  min-height: 100px;
  padding: 1.2rem 1.5rem;
  border-bottom: 1px solid var(--rule);
}

/*
  The mark is a link home, and a link home is a target: min-height holds the
  24 pixels a pointer needs on the day the type shrinks, and the inline-block
  is what lets a min-height apply at all.
*/
.wordmark {
  display: inline-block;
  min-height: 24px;
  font-family: var(--sans);
  font-size: 1.95rem;
  font-weight: 750;
  letter-spacing: -0.055em;
  line-height: 1.1;
  color: var(--ink);
  text-decoration: none;
}

/*
  deny, then first, then the full stop. The brand's red is the first half and
  the last character.

  The stop was white for a while and read as a typo: it sat inside the pale half
  of the word with nothing to attach it to, and a reader's eye stopped at
  "first". Red at both ends closes the word — the mark ends the sentence it
  begins, which is the whole of what the name says.
*/
.wordmark-deny, .wordmark-stop { color: var(--brand); }

.masthead-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.6rem;
  margin-left: auto;
  font-size: 0.92rem;
}

.masthead-nav a {
  color: var(--ink-soft);
  text-decoration: none;
}

.masthead-nav a:hover { color: var(--ink); }

/*
  The colour scheme switch. Drawn as text rather than an icon so it says what
  it does, and only present where theme.js runs to make it work.
*/
/*
  A sun or a moon, and nothing else on it.

  Square rather than a word in a box: the label said "Dark" while the page was
  light and "Light" while it was dark, which is correct and reads backwards to
  anybody who does not stop to think about it. A mark of what you are going to
  get needs no reading at all. 34px is the target a pointer needs, and the name
  a screen reader announces is on aria-label rather than in the box.
*/
.theme-toggle {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  cursor: pointer;
}

/*
  The mark takes the button's colour, so the hover below moves both: it is two
  pieces of geometry, not a typeface and not a file.
*/
.theme-toggle svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
}

/*
  One choice, shown as the name rather than as a chooser holding one item.
  Set in the same face as the chooser it replaces, so the block does not move
  when a name with parents is asked about next.
*/
.proof-single {
  margin: 0;
  font-family: var(--mono);
  font-size: 0.95rem;
  color: var(--ink);
}

/* The half that is filled is the scheme pressing the button gives you. */
.theme-toggle .theme-half {
  fill: currentColor;
  stroke: none;
}

.masthead-nav + .theme-toggle { margin-left: 0; }

.theme-toggle:hover { color: var(--ink); border-color: var(--ink-faint); }

/*
  The maker, named in the footer on an installation somebody runs, in the brand
  colour. Red is also the colour of an insecure verdict; in running text in the
  footer it does not read as one.
*/
.colophon-brand {
  color: var(--ink);
  font-weight: 700;
  letter-spacing: -0.03em;
}

/* ── The ask ──────────────────────────────────────────────────────────── */

main {
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 1.5rem;
}

.ask { padding: 3.6rem 0 2.4rem; }

h1 {
  margin: 0 0 1.1rem;
  font-size: clamp(2.2rem, 5.5vw, 3.6rem);
  line-height: 1.08;
  letter-spacing: -0.035em;
  font-weight: 600;
}

.lede {
  max-width: var(--measure);
  margin: 0 0 2.2rem;
  font-size: 1.06rem;
  color: var(--ink-soft);
}

.ask-form { margin: 0 0 1.1rem; }

.field-label {
  display: block;
  font-size: 0.74rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-faint);
  margin-bottom: 0.5rem;
}

.field-row {
  display: flex;
  gap: 0.6rem;
  max-width: var(--measure);
}

.field {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--sans);
  font-size: 1rem;
  padding: 0.75rem 0.9rem;
  color: var(--ink);
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}

.field:focus { border-color: var(--ink-faint); }

/*
  A field that takes a list rather than a value.

  One to a line, in the same face the report prints names in, so that what
  somebody pastes in looks like what comes back out. It resizes vertically and
  not horizontally: a list of host names has no long lines, and a box somebody
  can drag over the page beside it is a layout they have to put back.

  And it stops where the fields above it stop. A row of inputs is held to the
  measure, and a box for names that ran on to the page's edge was the one
  field in the column with its own right margin.
*/
.field-lines {
  display: block;
  width: 100%;
  max-width: var(--measure);
  font-family: var(--mono);
  font-size: 0.86rem;
  line-height: 1.6;
  resize: vertical;
}

/*
  Choosing a file, without the control the browser draws for one.

  The input itself is hidden and the label is the button, which is the only way
  to have a file picker that looks like the rest of a page. Nothing is
  uploaded: the file is read in the browser and its names go into the box
  above, so this is a way of typing rather than a way of sending.
*/
.field-row-file {
  align-items: center;
  gap: 0.7rem;
  margin-top: 0.6rem;
}

.button-file { cursor: pointer; }

.field-row-file .field-note {
  font-size: 0.86rem;
  color: var(--ink-soft);
}

.field::placeholder { color: var(--ink-faint); }

/* The one action a page asks for, in the accent. White on the accent holds
   the text threshold in both schemes. */
.submit {
  flex: 0 0 auto;
  font-family: var(--sans);
  font-size: 0.95rem;
  font-weight: 600;
  padding: 0.75rem 1.4rem;
  color: #ffffff;
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  cursor: pointer;
}

.submit:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }

/*
  A button that is working says so in words and in colour, never by fading.
  An opacity fades the label and its ground together below legibility, on the
  only thing a page shows for the seconds a scan takes; a softened ink says the
  same thing and keeps the word readable.
*/
.submit:disabled {
  cursor: progress;
  color: var(--paper);
  background: var(--ink-soft);
  border-color: var(--ink-soft);
}

.field-help,
.noscript {
  max-width: var(--measure);
  font-size: 0.82rem;
  color: var(--ink-faint);
}

.field-help { margin: 0.6rem 0 0; }

/*
  A field that follows something other than another field.

  The inventory's two extra inputs and the console's selectors each sat
  directly under the block above them — a list of checks, a paragraph of help —
  and read as part of it. The gap is the same everywhere rather than tuned per
  field, because what makes a column of inputs legible is that the space
  between them does not change.
*/
.field-group { margin-top: 1.6rem; }

/*
  And where a help paragraph is the last thing before the next field, it keeps
  the field's own measure rather than running the width of the page. A line
  somebody has to track back across is a line they read twice.
*/
.field-group .field-help { max-width: var(--measure); }


.noscript {
  margin: 1.4rem 0 0;
  padding: 0.9rem 1rem;
  background: var(--paper-sunk);
  border-left: 3px solid var(--ink-faint);
}

/* ── Result ───────────────────────────────────────────────────────────── */

.result {
  padding: 0 0 4rem;
  animation: settle 200ms ease-out;
}

@keyframes settle {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .result { animation: none; }
}

.summary {
  padding: 1.5rem 0;
  border-top: 2px solid var(--ink);
  border-bottom: 1px solid var(--rule);
}

/*
  The verdict and the thing it is a verdict on, on one line.

  The two sentences below the row are deliberately outside it. They were in
  it until 2026-09-01, inside the left column, and a dl is a block: the column
  took the whole width, and the stamp wrapped to a line of its own beneath
  four lines of coverage — underneath the sentence that explains it. Whatever
  is added here has to grow beside the stamp, not through it, which is why
  this row holds nothing that wraps to more than a few words.
*/
.summary-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
}

.summary-target {
  font-family: var(--mono);
  font-size: 1.28rem;
  font-weight: 600;
  margin: 0;
  overflow-wrap: anywhere;
}

.summary-meta {
  margin: 0.35rem 0 0;
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--ink-faint);
}

/*
  The signature of the page. A verdict is not a badge or a pill; it is a mark
  pressed onto a record, so it gets hard edges, weight, and letterspacing.
*/
.stamp {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 0.86rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  padding: 0.5rem 0.9rem 0.5rem 1.08rem;
  border: 2.5px solid currentColor;
}

.stamp-strong   { color: var(--strong); }
.stamp-weak     { color: var(--weak); }
.stamp-insecure { color: var(--insecure); }
.stamp-ungraded { color: var(--ink-faint); }

.section-title {
  font-family: var(--mono);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--ink-faint);
  margin: 2.6rem 0 0.9rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--rule);
}

/* ── Findings ─────────────────────────────────────────────────────────── */

.finding {
  padding: 0 0 0 1.05rem;
  margin: 0 0 1.9rem;
  border-left: 3px solid var(--rule);
}

.finding-strong   { border-left-color: var(--strong); }
.finding-weak     { border-left-color: var(--weak); }
.finding-insecure { border-left-color: var(--insecure); }

.finding-head {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  flex-wrap: wrap;
}

.finding-title {
  margin: 0;
  font-size: 1.02rem;
  font-weight: 620;
  letter-spacing: -0.012em;
}

.finding-rule {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--ink-faint);
}

.finding-body {
  max-width: var(--measure);
  margin: 0.45rem 0 0.7rem;
  color: var(--ink-soft);
  font-size: 0.94rem;
}

/*
  The citations are the argument. They are set as a strip rather than a
  footnote list so that they read as part of the finding, not as an appendix
  somebody can skip.
*/
.sources {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.9rem;
  font-size: 0.78rem;
}

.sources a { color: var(--link); }

/* ── Tables ───────────────────────────────────────────────────────────── */

.rows {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--mono);
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
}

.rows th {
  text-align: left;
  font-weight: 500;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.68rem;
  padding: 0 0.8rem 0.5rem 0;
  border-bottom: 1px solid var(--rule);
}

.rows td {
  padding: 0.42rem 0.8rem 0.42rem 0;
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}

/*
  Only the identifier column breaks mid-word. Suite names are long and contain
  no spaces, so on a narrow screen they need it; the short labels beside them
  do not, and are unreadable when it happens — "strong" split across two lines
  reads as "stron g".
*/
.rows td.identifier {
  overflow-wrap: anywhere;
  word-break: break-all;
}

/*
  A host name breaks, but not on purpose.

  The identifier column above is cipher suite names: long, unspaced, and
  nothing is lost by breaking one badly, so break-all is right there. A host
  name is a word somebody reads and then goes and looks at, and break-all split
  "denyfirst.dev" into two lines in a column with room for it — which reads as
  two names.

  `anywhere` without break-all is the trade this takes. It leaves the break
  available, so a two-hundred-and-fifty-character name cannot set the width of
  the table and push the columns beside it off the screen; it also lets the
  column be squeezed below a short name when the three columns beside it want
  the room, which is what a narrow window does. A broken name on a narrow
  window is recoverable. A table wider than the screen, with the column saying
  what each name is doing pushed off the right of it, is not.
*/
.rows td.hostname {
  overflow-wrap: anywhere;
}

.rows td:last-child, .rows th:last-child { padding-right: 0; }

/*
  Every cipher table has the same columns, so they are given the same columns.

  One table per protocol version, each sizing itself to its own contents:
  measured on a live report of kapitalbank.az, "Key exchange" began 92 pixels
  further right under TLS 1.2 than under TLS 1.3, and "Cipher" 68 pixels
  further left. Nothing was wrong with any row. A table exists so that a
  reader can run an eye down a column, and down two of these it could not.

  The widths sit on the <col> elements rather than on the header cells,
  because a fixed layout takes its geometry from the columns first and only
  then from the first row. That distinction matters here: the header is set
  smaller than the body, so a width written in ch on a <th> would be measured
  in the wrong font. A <col> inherits the table's own monospace, which is the
  font the values are set in.

  ch is the unit because every value in this table is fixed-width text of a
  known maximum length. The longest cipher suite name the standard library
  can print is TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, forty-five
  characters; the longest key exchange is "ephemeral (TLS 1.3)"; the longest
  cipher is "ChaCha20-Poly1305"; the longest grade is "insecure". Each column
  is given room for its own worst case and no more.
*/
.suites {
  table-layout: fixed;
  /* The four columns at their worst case, plus the three gutters between
     them. Written in ch so that it shrinks with the table on a narrow screen
     rather than reserving desktop widths on a phone. */
  min-width: calc(89ch + 2.4rem);
}

.suites .col-grade  { width: calc(8ch + 0.8rem); }
.suites .col-kex    { width: calc(19ch + 0.8rem); }
.suites .col-cipher { width: 17ch; }

/* Declared rather than left out. The suite column takes whatever the other
   three do not, and saying so is what the other three are measured against. */
.suites .col-suite  { width: auto; }

/*
  A suite name is an identifier, and an identifier is never broken.

  The column beside it used to break mid-word on a narrow screen, so
  TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 arrived as three fragments and a
  reader copying it by hand could produce a name that is not a cipher suite.
  The name is the finding here. It is kept whole and the container is allowed
  to scroll instead.
*/
.suites td.identifier {
  white-space: nowrap;
  overflow-wrap: normal;
  word-break: normal;
}

/*
  The container scrolls, and says so.

  A table wider than the screen that gives no sign of it hides its right-hand
  columns completely: a reader on a phone would see Grade and Suite and never
  learn that the mode of encryption was recorded at all. The two shadows are
  painted on the box and the two paper-coloured covers travel with the
  content, so each shadow is hidden exactly while that edge is at the end of
  its travel and shows the moment there is something past it.
*/
.table-scroll {
  overflow-x: auto;
  background:
    linear-gradient(to right, var(--paper), rgba(0, 0, 0, 0)) left / 1.4rem 100% no-repeat local,
    linear-gradient(to left,  var(--paper), rgba(0, 0, 0, 0)) right / 1.4rem 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, var(--edge), rgba(0, 0, 0, 0)) left / 0.5rem 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, var(--edge), rgba(0, 0, 0, 0)) right / 0.5rem 100% no-repeat scroll;
}

/*
  Paper does not scroll.

  On a screen a table too wide for its column is scrolled to. On a sheet of
  paper the part past the edge is simply not there, and a printed report that
  drops the mode of encryption is worse than one that breaks a suite name
  across two lines. So on paper the container stops scrolling, the table
  gives up its minimum, and the identifier is allowed to wrap after all —
  the last resort, and only where there is no other way to show it.
*/
@media print {
  .table-scroll { overflow-x: visible; background: none; }
  .suites { min-width: 0; }

  /* Shares of the sheet rather than counts of characters, because the sheet
     is what there is. Both tables are given the same shares, so the columns
     still line up on paper; the suite column takes the remaining 42% and is
     the only one allowed to wrap. */
  .suites .col-grade  { width: 12%; }
  .suites .col-kex    { width: 24%; }
  .suites .col-cipher { width: 22%; }

  /* Last resort, and only where a value cannot fit its column on its own
     line. break-word leaves everything that does fit exactly as it is. */
  .suites td { overflow-wrap: break-word; }
  .suites td.identifier { white-space: normal; overflow-wrap: anywhere; }
}

.mark-strong   { color: var(--strong); }
.mark-weak     { color: var(--weak); }
.mark-insecure { color: var(--insecure); }
.mark-faint    { color: var(--ink-faint); }

.group-label {
  font-family: var(--mono);
  font-size: 0.74rem;
  color: var(--ink-faint);
  margin: 1.5rem 0 0.5rem;
}

/*
  Why a row says what it says.

  A version that was not measured carries the probe's own sentence underneath
  the word, so "not measured" is never left to be guessed at. Small and faint
  because it is a qualification of the cell above it rather than a second
  reading, and it must not compete with the grade column beside it.
*/
.rows td .row-note {
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--ink-faint);
  margin: 0.22rem 0 0;
  max-width: 24rem;
}

/*
  A list that stopped early, said where the list is rather than only in the
  folded notes.

  Coloured as a warning rather than faint. The suites missing from a truncated
  enumeration are the weak ones, so this is the sentence that stops the table
  above it from being read as the whole answer, and a grey line under a
  heading is read as a caption.
*/
.group-note {
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--weak);
  margin: -0.25rem 0 0.6rem;
  max-width: 42rem;
}

/* What a section is, under its title. Faint, because it explains and does not warn. */
.section-note {
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--ink-faint);
  margin: -0.25rem 0 0.6rem;
  max-width: 42rem;
}

/*
  And a note that follows a table or a list rather than a title.

  The pull upward is for tucking a note under its heading. Under a table it
  pulled the sentence four pixels into the last row — "Their addresses are in
  four networks" sat on the name servers it was about — so a note after rows
  of anything is given room instead.
*/
:is(table, .rows, .pairs, ul, ol, dl) + :is(.section-note, .group-note) {
  margin-top: 0.75rem;
}

/* ── Certificate ──────────────────────────────────────────────────────── */

.pairs {
  display: grid;
  grid-template-columns: minmax(6.5rem, max-content) 1fr;
  gap: 0.42rem 1.2rem;
  font-family: var(--mono);
  font-size: 0.82rem;
}

.pairs dt {
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.68rem;
  padding-top: 0.12rem;
}

.pairs dd {
  margin: 0;
  overflow-wrap: anywhere;
}

/* ── Notes and errors ─────────────────────────────────────────────────── */

.notes {
  max-width: var(--measure);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.86rem;
  color: var(--ink-soft);
}

.notes li {
  padding-left: 1rem;
  margin-bottom: 0.6rem;
  border-left: 1px solid var(--rule);
}

/*
  The limits of a scan, folded.

  They are never hidden — the count is in the summary line, so a reader sees
  that there are three of them before deciding whether to read three of them.
  Silence would be the failure; a heading that has to be opened is not
  silence.

  The marker is the browser's own triangle, kept rather than replaced. It is
  the one affordance every reader already knows, and a redrawn one would be a
  smaller triangle that means the same thing.
*/
.notes-section {
  margin: 2.6rem 0 0;
  padding-top: 0.5rem;
  border-top: 1px solid var(--rule);
}

/*
  Three sections, one block.

  They are siblings and each carries the rule above it, so the first is
  separated from the report and the rest are separated from each other by the
  same line at a smaller distance. Without this the three read as three
  unrelated boxes, which is the layout this replaced.
*/
.notes-section + .notes-section { margin-top: 0.9rem; }

/*
  Two display declarations, and the order is the point.

  A summary draws its triangle because the user agent gives it
  display: list-item. Setting display: flex replaces that outer display and
  the triangle goes with it — which is how the comment above came to promise
  a marker the stylesheet had already removed.

  The two-value syntax keeps both: list-item for the marker, flex for the
  layout. A browser that does not understand it ignores the second line and
  keeps the first, which is exactly the behaviour this file had before.

  The marker becomes the first flex item when it is drawn, so the count is
  pushed right with margin-left rather than with justify-content. That places
  it correctly whether there are two items or three, so the fallback and the
  modern path lay out the same.
*/
.notes-head {
  display: flex;
  display: list-item flex;
  align-items: baseline;
  /* The count goes under the title when the two do not fit on one line. At
     320 pixels "Observed" and "6 measured, not graded" came to 261 pixels in a
     238-pixel line, and the count ran past the edge of its panel. */
  flex-wrap: wrap;
  gap: 0.55rem;
  cursor: pointer;
  padding: 0.35rem 0;
  list-style-position: inside;
}

.notes-head::-webkit-details-marker { color: var(--ink-faint); }
.notes-head::marker { color: var(--ink-faint); }

.notes-title,
.notes-count {
  font-family: var(--mono);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--ink-faint);
}

.notes-count { flex: 0 0 auto; margin-left: auto; }

.notes-section:hover .notes-title,
.notes-section[open] .notes-title { color: var(--ink-soft); }

.notes-section .notes { margin-top: 0.7rem; }

.failure {
  max-width: var(--measure);
  margin: 2rem 0 0;
  padding: 1rem 1.1rem;
  background: var(--paper-sunk);
  border-left: 3px solid var(--insecure);
}

.failure p { margin: 0; }
.failure p + p { margin-top: 0.5rem; font-size: 0.85rem; color: var(--ink-faint); }

.working {
  font-family: var(--mono);
  font-size: 0.84rem;
  color: var(--ink-faint);
  padding: 2rem 0;
}

/* ── Colophon ─────────────────────────────────────────────────────────── */

.colophon {
  max-width: var(--page);
  margin: 0 auto;
  padding: 2rem 1.5rem;
  border-top: 1px solid var(--rule);
  font-size: 0.82rem;
  color: var(--ink-faint);
}

/*
  The measure is for the colophon's prose, and the row of links is not prose.

  This was `.colophon p`, and the link row is written as a paragraph, so it
  inherited a 34rem measure — the width running text can be read at. The five
  links come to 677 pixels. They broke onto a second line inside a footer 884
  pixels wide, with 207 to spare, because a constraint on reading sentences
  had been applied to a row of links.

  Excluded here rather than overridden below: `.colophon p` beats a bare
  `.colophon-links` on specificity whatever the order, so an override there
  would have had to be written stronger than it looks, and the next person to
  read it would not know why.
*/
.colophon p:not(.colophon-links) { max-width: var(--measure); }
.colophon p { margin: 0 0 0.9rem; }
/* They still wrap on a phone, where wrapping is the right answer. */
.colophon-links {
  display: flex;
  gap: 1.3rem;
  flex-wrap: wrap;
}

@media (max-width: 34rem) {
  .field-row { flex-direction: column; }
  .submit { width: 100%; }
  .pairs { grid-template-columns: 1fr; gap: 0.1rem 0; }
  .pairs dd { margin-bottom: 0.6rem; }
    /* Narrower columns leave the identifiers more room to sit on one line. */
  .rows { font-size: 0.74rem; }
  .rows th, .rows td { padding-right: 0.5rem; }
}

/* ── Prose pages ──────────────────────────────────────────────────────── */

.prose {
  padding: 3.2rem 0 3rem;
  max-width: var(--measure);
}

.prose h1 {
  margin-bottom: 1.1rem;
}

.prose h2 {
  margin: 3rem 0 0.8rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--rule);
  font-size: 1.12rem;
  font-weight: 640;
  letter-spacing: -0.014em;
}

.prose h3 {
  margin: 1.9rem 0 0.6rem;
  font-size: 0.96rem;
  font-weight: 620;
  color: var(--ink);
}

.prose p {
  margin: 0 0 1rem;
  color: var(--ink-soft);
}

.prose .lede {
  color: var(--ink-soft);
  font-size: 1.02rem;
  margin-bottom: 2rem;
}

.prose a { color: var(--link); }

.prose code {
  font-family: var(--mono);
  font-size: 0.88em;
  color: var(--ink);
}

/*
  A command-line flag is one word.

  A line may break after a hyphen, and the leading hyphen of a flag is one, so
  "-verification-secret-file" broke into "-" at the end of one line and the
  rest at the start of the next: two things that read as neither, and a reader
  copying one line gets a flag this program does not have.
*/
code.flag { white-space: nowrap; }

/*
  One page now answers what used to be three, so it needs a way in. The jump
  links matter most for the reader who arrived from a scanning notice and
  wants one section, not the whole document.
*/
.jump {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.1rem;
  margin: 0 0 2.6rem;
  padding: 0.9rem 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.78rem;
}

/* Anchored headings should not land under the top of the window. */
.prose h2[id] { scroll-margin-top: 1.5rem; }

.plain {
  list-style: none;
  padding: 0;
  margin: 0 0 1rem;
  color: var(--ink-soft);
}

.plain li {
  padding-left: 1rem;
  margin-bottom: 0.55rem;
  border-left: 1px solid var(--rule);
}

.prose .colophon-links {
  margin-top: 2.2rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--rule);
}
/*
  The two ways to take the report away, side by side and quiet: they are an
  offer rather than the point of the page.
*/
.summary-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0 1.2rem;
  margin: 0.6rem 0 0;
}

/* The link that hands a reader the report they are looking at, and the button
   beside it that hands them a sheet of paper. One face for both, because they
   are one offer made twice. */
.download {
  display: inline-block;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--ink-faint);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.download:hover,
.download:focus-visible {
  color: var(--ink);
}

/*
  The pointer that replaced a section.

  Set in the same small uppercase as the section headings so it reads as part
  of that block rather than as a stray sentence after it, and left open — a
  link folded inside a summary is one nobody follows.
*/
.notes-method {
  margin: 1.1rem 0 0;
  padding-top: 0.6rem;
  border-top: 1px solid var(--rule);
  font-size: 0.85rem;
  color: var(--ink-faint);
}

.notes-method-link { color: var(--ink-soft); }

/*
  The two sentences under the verdict.

  Both belong to the summary rather than to a block of their own: one says
  what a verdict means and the other says how much of the picture it rests on,
  and a reader meets them in the two seconds they spend on the stamp.

  Quiet by design. What replaced them here was a nine-line block that restated
  the tables below it, and the lesson of that is not to shout louder.
*/
.summary-pairs { margin: 1rem 0 0; }

.summary-worst,
.summary-coverage {
  max-width: var(--measure);
  margin: 0.7rem 0 0;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--ink-faint);
}

.summary-worst { color: var(--ink-soft); }

/* ── The console ─────────────────────────────────────────────────────────

   The surface a self-hosted installation puts at "/".

   Deliberately not the look of the pages at /tls and /web. Those are written
   for somebody who arrived from a log line and is deciding whether to worry,
   so they lead with a sentence and give the argument room. This is written for
   the person who installed it, who decided already: the field is the first
   thing, the type is smaller, and the space between things is tighter, because
   what they want is density and an answer rather than persuasion.

   Same tokens throughout. A second palette would be a second thing to keep in
   step, and the colours already carry meaning a reader has learned. */

.console {
  max-width: 54rem;
  margin: 0 auto;
}

/* A heading for assistive technology, and nothing on the screen: the header
   already names the tool. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.console-form { margin: 1.6rem 0 2rem; }

/* The check list. A row each, so the rule set that will grade a report is
   readable beside the box that runs it — the string a reader needs in order to
   know whether two reports are comparable at all. */
.checks {
  border: 0;
  padding: 0;
  margin: 1.1rem 0 0;
}

.check {
  display: grid;
  grid-template-columns: auto minmax(5.5rem, auto) 1fr auto;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.42rem 0;
  border-bottom: 1px solid var(--rule);
  cursor: pointer;
}

.check:last-child { border-bottom: 0; }

.check-name {
  font-family: var(--mono);
  font-size: 0.86rem;
  font-weight: 700;
  color: var(--ink);
}

.check-says {
  font-size: 0.86rem;
  color: var(--ink-soft);
}

.check-policy {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--ink-faint);
  white-space: nowrap;
}

.console-results { margin-top: 2rem; }

/* One check's block. The heading carries the verdict, so a reader scrolling
   three reports can see where they stand without opening any of them. */
.run {
  border-top: 2px solid var(--ink);
  padding-top: 0.9rem;
  margin-bottom: 2.6rem;
}

.run-head {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 0.3rem 1rem;
  margin-bottom: 1.1rem;
}

.run-name {
  font-family: var(--mono);
  font-size: 0.96rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin: 0;
  color: var(--ink);
}

.run-says {
  grid-column: 1;
  margin: 0;
  font-size: 0.84rem;
  color: var(--ink-soft);
}

/* Empty once the report below carries the verdict: one result, said once. */
.run-state:empty { display: none; }

.run-state {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  margin: 0;
  font-family: var(--mono);
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
}

.run-body { min-height: 1.4rem; }

/* A plain two-column table, used where a report has rows rather than a chain:
   what a zone publishes, and what this installation is. */
.grid {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.grid th {
  text-align: left;
  font-family: var(--mono);
  font-size: 0.76rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  padding: 0.4rem 1.2rem 0.4rem 0;
  white-space: nowrap;
  vertical-align: baseline;
  width: 1%;
}

.grid td {
  padding: 0.4rem 0;
  color: var(--ink);
  vertical-align: baseline;
  /* A value is whatever was read: an IPv6 address, a mailto: address, a
     header with no spaces in it. Allowed to break where it has to, the way
     .pairs dd is, so the longest one cannot set the table's width. */
  overflow-wrap: anywhere;
}

/* The one table here with two columns of values, the zone's servers and their
   addresses, had no gap between them: a name as wide as its column ran
   straight into the address beside it. The same gutter the label column has. */
.grid td:not(:last-child) { padding-right: 1.2rem; }

/*
  On a phone the labels wrap too.

  Kept on one line, the longest label set the first column on every row:
  "Cross-Origin-Embedder-Policy" took 251 of the 342 pixels a 390-pixel phone
  leaves the report, the table came to 441, and the page scrolled sideways —
  on every report drawn with this table, which is every check but Transport,
  whose rows are .pairs and stack. Wrapped at its spaces and hyphens, a label
  takes the width of its longest word.

  Not `anywhere` here, unlike the value beside it: with the column sized to
  its narrowest content, that would let a label shrink to one letter a line.
*/
@media (max-width: 34rem) {
  .grid th { white-space: normal; }
  .grid th, .grid td:not(:last-child) { padding-right: 0.8rem; }
}

.console-state {
  margin-top: 3rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--rule);
}

.state-grid {
  display: grid;
  grid-template-columns: minmax(6rem, auto) 1fr;
  gap: 0.55rem 1.4rem;
  margin: 0.9rem 0 0;
  font-size: 0.88rem;
}

.state-grid dt {
  font-family: var(--mono);
  font-size: 0.74rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
}

.state-grid dd {
  margin: 0;
  color: var(--ink-soft);
}

.state-policy {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.74rem;
  color: var(--ink);
  margin-right: 0.7rem;
}

/*
  The proof dialog. The record is the thing to copy, so it is set in the mono
  face and allowed to break anywhere: a token is one long word, and a dialog
  that scrolls sideways on a phone hides half of it.
*/
.proof {
  width: min(36rem, calc(100vw - 2rem));
  max-height: calc(100vh - 2rem);
  overflow: auto;
  padding: 1.4rem 1.5rem;
  color: var(--ink);
  background: var(--paper);
  border: 1.5px solid var(--ink);
  border-radius: 0;
}

.proof::backdrop { background: rgba(0, 0, 0, 0.55); }

.proof-lede {
  margin: 0 0 1.1rem;
  font-size: 0.92rem;
  color: var(--ink-soft);
}

.proof-level {
  font-family: var(--mono);
  font-size: 0.95rem;
  padding: 0.45rem 0.6rem;
  color: var(--ink);
  background: var(--paper);
  border: 1.5px solid var(--ink);
  border-radius: 0;
  max-width: 100%;
}

.proof-hint {
  margin: 0.4rem 0 0.9rem;
  font-size: 0.78rem;
  color: var(--ink-faint);
}

/*
  Where the name goes at the common DNS providers. Folded, because most
  people know their own provider's form and the record above is what matters.
*/
.provider-help {
  margin: 0.4rem 0 0.9rem;
  font-size: 0.8rem;
  color: var(--ink-soft);
}

.provider-help > summary {
  cursor: pointer;
  font-size: 0.78rem;
  color: var(--ink-faint);
  padding: 0.2rem 0;
}

.provider-help p { margin: 0.5rem 0; }
.provider-help .rows { margin: 0.5rem 0; font-size: 0.76rem; }

.proof-record {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.5rem 1rem;
  margin: 0.9rem 0 0;
  padding: 0.9rem 1rem;
  background: var(--paper-sunk);
}

.proof-record dt {
  font-family: var(--mono);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  color: var(--ink-faint);
  padding-top: 0.2rem;
}

.proof-record dd {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
}

.proof-record code {
  font-size: 0.86rem;
  overflow-wrap: anywhere;
  word-break: break-all;
}

.proof-copy,
.proof-cancel {
  font-family: var(--sans);
  font-size: 0.8rem;
  min-height: 24px;
  padding: 0.2rem 0.6rem;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 0;
  cursor: pointer;
}

.proof-copy:hover,
.proof-cancel:hover { border-color: var(--ink); }

.proof-status {
  min-height: 1.4em;
  margin: 1rem 0 0.8rem;
  font-size: 0.86rem;
  color: var(--ink-soft);
}

.proof-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
}

.proof-cancel { padding: 0.6rem 1rem; font-size: 0.9rem; }

@media (max-width: 34rem) {
  .check {
    grid-template-columns: auto 1fr;
    row-gap: 0.15rem;
  }
  .check-says,
  .check-policy { grid-column: 2; }

  .state-grid { grid-template-columns: 1fr; gap: 0.15rem 0; }
  .state-grid dd { margin-bottom: 0.7rem; }
}

/* ── The front page and the Porch page ──────────────────────────────────

   The demonstration's own pages: the denyfirst front page, and Porch shown
   running. Built from the same tokens as everything else, so the report a
   visitor gets here is the report they would get from their own copy. */

.eyebrow {
  margin: 0 0 0.9rem;
  font-size: 0.74rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
}

.hero {
  padding: 4.5rem 0 1rem;
}

.hero h1 {
  font-size: clamp(2.8rem, 7vw, 5.4rem);
  line-height: 1.04;
  letter-spacing: -0.045em;
  margin-bottom: 2rem;
}

.hero-soft { color: var(--ink-faint); }

.hero-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem 2.5rem;
}

.hero-bottom p {
  max-width: var(--measure);
  margin: 0;
  font-size: 1.1rem;
  color: var(--ink-soft);
}

.button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0.7rem 1.3rem;
  font-size: 0.95rem;
  font-weight: 600;
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
}

.button-primary {
  color: #ffffff;
  background: var(--accent);
  border-color: var(--accent);
}

.button-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }

.button-secondary {
  color: var(--ink);
  background: var(--paper-sunk);
  border-color: var(--rule);
}

.button-secondary:hover { border-color: var(--ink-faint); }

.text-link {
  display: inline-block;
  font-size: 0.95rem;
  font-weight: 600;
  padding: 0.3rem 0;
}

.band {
  padding: 3.6rem 0;
  border-bottom: 1px solid var(--rule);
}

.band[id] { scroll-margin-top: 1rem; }

/*
  The note beside a band's title is centred on the heading beside it.

  It hung from the heading's bottom edge. A note of three short lines beside a
  title of two large ones then began well below the title's first line and
  ended below its last, and the pair read as the note having slipped down the
  page. Sharing the last baseline instead moved it two pixels and did not
  change that; centred on the heading — the eyebrow and the title together —
  it sits beside the block it belongs to. Measured on every band on both
  pages; where the two wrap onto separate lines on a narrow screen, this has
  nothing to align.
*/
.band-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 2.5rem;
  margin-bottom: 2.2rem;
}

.band-title {
  margin: 0;
  font-size: clamp(1.8rem, 3.6vw, 2.5rem);
  line-height: 1.15;
  letter-spacing: -0.03em;
  font-weight: 600;
}

.band-note {
  max-width: 22rem;
  margin: 0;
  font-size: 0.98rem;
  color: var(--ink-soft);
}

.band-footnote {
  margin: 1.2rem 0 0;
  font-size: 0.85rem;
  color: var(--ink-faint);
}

.band-split {
  display: grid;
  grid-template-columns: minmax(10rem, 1fr) 2fr;
  gap: 1.5rem 2.5rem;
}

.band-prose {
  max-width: var(--measure);
  margin: 1.4rem 0;
  font-size: 1.02rem;
  color: var(--ink-soft);
}

.cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
}

.card {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 1.5rem 1.5rem 1.2rem;
  color: var(--ink);
  text-decoration: none;
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: calc(var(--radius) + 2px);
}

.card p {
  margin: 0.6rem 0 1.4rem;
  font-size: 0.95rem;
  color: var(--ink-soft);
}

.card-live { border-color: var(--accent); }

a.card:hover { border-color: var(--link); }

.card-name {
  margin: 1.4rem 0 0;
  font-size: 1.6rem;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.card-foot {
  margin-top: auto;
  padding-top: 0.9rem;
  border-top: 1px solid var(--rule);
  font-size: 0.74rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-faint);
}

.badge {
  align-self: flex-start;
  padding: 0.2rem 0.55rem;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  border: 1px solid var(--rule);
  border-radius: 4px;
}

.badge-live {
  color: var(--link);
  border-color: var(--accent);
}

.columns {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.5rem 2.5rem;
}

.columns article {
  padding-top: 1.2rem;
  border-top: 1px solid var(--rule);
}

.columns h3 {
  margin: 0.8rem 0 0.6rem;
  font-size: 1.2rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.columns p { margin: 0; color: var(--ink-soft); font-size: 0.96rem; }

.columns .index {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--link);
}

.porch-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 2.5rem 3.5rem;
  align-items: center;
  padding: 3.6rem 0;
  border-bottom: 1px solid var(--rule);
}

.porch-hero h1 {
  font-size: clamp(2.4rem, 5vw, 3.8rem);
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 1.2rem;
}

.chips span {
  padding: 0.3rem 0.65rem;
  font-size: 0.82rem;
  color: var(--ink-soft);
  border: 1px solid var(--rule);
  border-radius: 4px;
}

.check-panel {
  padding: 1.6rem;
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: calc(var(--radius) + 3px);
}

.check-panel-title {
  margin: 0 0 1.4rem;
  font-size: 1.7rem;
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-weight: 600;
}

.check-panel .field { width: 100%; background: var(--paper); }

.check-panel .check { grid-template-columns: auto minmax(4.5rem, auto) 1fr; }

.check-panel .check-policy { display: none; }

/*
  The rows share their columns.

  Each row was a grid of its own, so each sized its name column to its own
  name: "Transport" pushed its sentence two pixels right of the rest, and the
  row that opens a page, with no box to put first, began a whole column early —
  at a phone's width it did not even stack the way the others did. One grid
  with a subgrid per row sizes the name column to the longest name and puts
  every sentence on one line. A browser without subgrid keeps the fixed
  columns declared first, which line up too.
*/
.check-panel .check-rows {
  display: grid;
  grid-template-columns: 1.1rem 6rem 1fr;
  grid-template-columns: 1.1rem max-content 1fr;
  column-gap: 0.7rem;
}

.check-panel .check-rows .check {
  grid-column: 1 / -1;
  grid-template-columns: 1.1rem 6rem 1fr;
  grid-template-columns: subgrid;
  column-gap: 0.7rem;
}

.check-panel .check-rows .check > input,
.check-panel .check-door { justify-self: center; }

.check-panel .check-door {
  color: var(--ink-faint);
  transition: transform 160ms ease-out;
}

.check-panel .check-elsewhere:hover .check-door { transform: translate(2px, -2px); }

@media (max-width: 34rem) {
  .check-panel .check-rows { grid-template-columns: 1.1rem 1fr; }
  .check-panel .check-rows .check-says { grid-column: 2; }
}

@media (prefers-reduced-motion: reduce) {
  .check-panel .check-door { transition: none; }
}

.submit-wide {
  width: 100%;
  margin-top: 1.2rem;
}

.porch-report {
  padding: 2.4rem 0 1rem;
  scroll-margin-top: 1rem;
}

.porch-report-head { margin-bottom: 1.2rem; }

.porch-report-title {
  margin: 0;
  font-size: 1.9rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  overflow-wrap: anywhere;
}

/* One tab per check. Each carries the check's own state, so the verdict of
   one is never read as the verdict of the others. */
.tabs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: 0.8rem;
  margin: 0 0 1.2rem;
}

.tab {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.35rem 1rem;
  align-items: baseline;
  padding: 1rem 1.1rem;
  font-family: var(--sans);
  text-align: left;
  color: var(--ink);
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  cursor: pointer;
}

.tab[aria-selected="true"] {
  border-color: var(--accent);
  box-shadow: inset 0 -2px 0 var(--accent);
}

.tab-name {
  font-size: 1.05rem;
  font-weight: 600;
}

.tab-state {
  font-family: var(--mono);
  font-size: 0.74rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-faint);
}

/* The verdict colours, which the grey above would otherwise override. */
.tab-state.stamp-strong   { color: var(--strong); }
.tab-state.stamp-weak     { color: var(--weak); }
.tab-state.stamp-insecure { color: var(--insecure); }

.tab-says {
  grid-column: 1 / -1;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

.tab-panel {
  padding: 1.4rem 1.5rem;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}

.tab-panel .summary { border-top: 0; padding-top: 0; }

.cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.2rem 2rem;
  margin: 3rem 0;
  padding: 1.8rem;
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: calc(var(--radius) + 2px);
}

.cta p { margin: 0; color: var(--ink-soft); }

.cta-title {
  margin: 0 0 0.4rem;
  font-size: 1.6rem;
  font-weight: 600;
  letter-spacing: -0.02em;
}

@media (max-width: 52rem) {
  .cards,
  .columns,
  .porch-hero,
  .band-split { grid-template-columns: 1fr; }
}

@media (max-width: 34rem) {
  .masthead-nav { order: 3; width: 100%; margin-left: 0; }
  .masthead-nav + .theme-toggle { margin-left: auto; }
  .hero { padding-top: 2.8rem; }
  .check-panel { padding: 1.2rem; }
  .tab-panel { padding: 1rem; }
}

/* ── Finishing ───────────────────────────────────────────────────────────

   The small things the design carries: an arrow on a link that goes
   somewhere, a red dot before a heading's label, a short red rule under the
   hero, a symbol on a card. Every arrow is drawn by CSS rather than written in
   the markup, with empty alternative text, so a screen reader announces the
   link and not "north east arrow", and a script that rewrites a button's label
   cannot lose it. */

.arrow-ne::after,
.arrow-se::after,
.arrow-e::after,
.arrow-down::after {
  display: inline-block;
  margin-left: 0.55em;
  transition: transform 160ms ease-out;
}

.arrow-ne::after   { content: "↗"; content: "↗" / ""; }
.arrow-se::after   { content: "↘"; content: "↘" / ""; }
.arrow-e::after    { content: "→"; content: "→" / ""; }
.arrow-down::after { content: "↓"; content: "↓" / ""; }

a:hover.arrow-ne::after,
.card-link:hover .arrow-ne::after,
button:hover.arrow-ne::after { transform: translate(2px, -2px); }
a:hover.arrow-se::after      { transform: translate(2px, 2px); }
a:hover.arrow-e::after       { transform: translateX(3px); }
a:hover.arrow-down::after    { transform: translateY(2px); }

.card-name.arrow-ne {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

.card-name.arrow-ne::after {
  font-size: 1.1rem;
  font-weight: 400;
  color: var(--ink-faint);
}

.eyebrow-dot::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  margin: 0 0.6rem 0.12em 0;
  border-radius: 50%;
  background: var(--brand);
}

.hero-rule {
  position: relative;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin: 3rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
}

.hero-rule::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 4rem;
  height: 2px;
  background: var(--brand);
}

.card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.card-symbol {
  font-size: 1.9rem;
  line-height: 1;
  color: var(--brand);
}

.card-link {
  transition: transform 180ms ease-out, border-color 180ms ease-out;
}

.card-link:hover { transform: translateY(-3px); }

.cards-two { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.check-panel-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 0 0 0.9rem;
}

.check-panel-top .eyebrow { margin: 0; }

.live {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.74rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--link);
}

.live::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--brand);
}

.docs-hero {
  padding: 3.8rem 0 2rem;
}

.docs-hero .lede { margin-bottom: 0; }

/* The footer: who made it on one side, four ways on at the other. */
.colophon-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem 2rem;
}

.colophon-row .colophon-links { margin: 0; }


@media (prefers-reduced-motion: reduce) {
  .card-link,
  .arrow-ne::after,
  .arrow-se::after,
  .arrow-e::after,
  .arrow-down::after { transition: none; }
  .card-link:hover { transform: none; }
}

@media (max-width: 52rem) {
  .cards-two { grid-template-columns: 1fr; }
}

/* Quiet links in the footer, as the design has them; they brighten on hover
   and keep an underline only then. */
.colophon-links a {
  color: var(--ink-soft);
  text-decoration: none;
}

.colophon-links a:hover {
  color: var(--ink);
  text-decoration: underline;
}

/* The last section needs no rule of its own: the footer draws one. */
main > .band:last-child { border-bottom: 0; }


/* The Products menu. Opens on hover and on keyboard focus, and needs no
   script; the list sits under its button with a small bridge so the pointer
   can travel to it. */
.nav-menu { position: relative; }

.nav-menu-button::after {
  content: "▾";
  content: "▾" / "";
  margin-left: 0.35em;
  font-size: 0.8em;
}

.nav-menu-list {
  display: none;
  position: absolute;
  top: 100%;
  left: -1rem;
  z-index: 10;
  min-width: 16rem;
  margin: 0;
  padding: 0.5rem;
  list-style: none;
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.25);
}

.nav-menu-list::before {
  content: "";
  position: absolute;
  top: -0.8rem;
  left: 0;
  right: 0;
  height: 0.8rem;
}

.nav-menu:hover .nav-menu-list,
.nav-menu:focus-within .nav-menu-list { display: block; margin-top: 0.6rem; }

.nav-menu-list a,
.nav-menu-soon {
  display: grid;
  gap: 0.1rem;
  padding: 0.55rem 0.7rem;
  border-radius: calc(var(--radius) - 2px);
  text-decoration: none;
}

.nav-menu-list a:hover,
.nav-menu-list a:focus-visible { background: var(--paper); }

.nav-menu-name {
  font-weight: 600;
  color: var(--ink);
}

.nav-menu-note {
  font-size: 0.78rem;
  color: var(--ink-faint);
}

.nav-menu-soon .nav-menu-name { color: var(--ink-soft); }

@media (hover: none) {
  .nav-menu:hover .nav-menu-list { display: none; }
}

/* The footer's left side: the mark, as the header draws it and a size down,
   and one line beside it. Both sentences sit on that line, so the footer is a
   single row wherever there is room for one. */
.colophon-by {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.2rem 0.9rem;
}

.colophon .colophon-by p { margin: 0; }

.colophon-mark .colophon-brand {
  font-size: 1.35rem;
  font-weight: 750;
  letter-spacing: -0.05em;
}

/*
  Three numbered steps, one row each: what the step is for on the left, the
  commands on the right, wide enough to read without scrolling.
*/
.steps {
  display: grid;
  gap: 0.8rem;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
}

.steps li {
  counter-increment: step;
  display: grid;
  grid-template-columns: minmax(0, 18rem) minmax(0, 1fr);
  grid-template-rows: auto auto 1fr;
  column-gap: 2rem;
  align-items: start;
  padding: 1.4rem;
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: calc(var(--radius) + 2px);
}

.steps li::before {
  content: "0" counter(step);
  grid-column: 1;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--link);
}

.steps h3 {
  grid-column: 1;
  margin: 0.4rem 0 0.35rem;
  font-size: 1.2rem;
  font-weight: 600;
}

.steps p {
  grid-column: 1;
  margin: 0;
  font-size: 0.93rem;
  color: var(--ink-soft);
}

/*
  A command, drawn as the terminal it is typed into, and the button that
  copies it.

  Dark in both schemes, because that is what a terminal looks like and what a
  reader is about to paste into. The bar says where the command runs — on the
  server, or on your own computer — which was the one thing the old boxes left
  a reader to work out.

  The prompt is drawn by CSS, so it is on the screen and never in what Copy
  writes: the button copies the element's text, and generated content is not
  text (see copyRecord in app.js). Each colour is held to 4.5:1 against the
  panel, the same threshold as the body text.
*/
.terminal {
  min-width: 0;
  overflow: hidden;
  background: var(--term-bg);
  border: 1px solid var(--term-rule);
  border-radius: calc(var(--radius) + 2px);
  box-shadow: 0 10px 24px -14px rgba(0, 0, 0, 0.55);
}

.terminal-bar {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  min-height: 2.3rem;
  padding: 0.3rem 0.5rem 0.3rem 0.85rem;
  background: var(--term-bar);
  border-bottom: 1px solid var(--term-rule);
}

/* Three dots, one element: the other two are its shadows. */
.terminal-dots {
  flex: none;
  width: 0.62rem;
  height: 0.62rem;
  margin-right: 2rem;
  border-radius: 50%;
  background: #ff5f57;
  box-shadow: 1rem 0 0 #febc2e, 2rem 0 0 #28c840;
}

.terminal-where {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--term-faint);
}

.terminal pre {
  margin: 0;
  padding: 0.95rem 1.1rem 1rem;
  overflow-x: auto;
  background: transparent;
}

.terminal pre code {
  font-family: var(--mono);
  font-size: 0.82rem;
  line-height: 1.8;
  color: var(--term-ink);
  white-space: pre;
}

.terminal .ln::before {
  content: "$ ";
  color: var(--term-prompt);
  user-select: none;
}

.t-c   { color: var(--term-cmd); }
.t-f   { color: var(--term-flag); }
.t-s   { color: var(--term-str); }
.t-v   { color: var(--term-var); }
.t-o   { color: var(--term-faint); }
.t-key { color: var(--term-cmd); }

.command-copy {
  margin-left: auto;
  font-family: var(--sans);
  font-size: 0.78rem;
  min-height: 24px;
  padding: 0.2rem 0.65rem;
  color: var(--term-ink);
  background: transparent;
  border: 1px solid var(--term-rule);
  border-radius: var(--radius);
  cursor: pointer;
}

.command-copy:hover,
.command-copy:focus-visible { border-color: var(--term-faint); }

.steps .command {
  grid-column: 2;
  grid-row: 1 / 4;
  align-self: center;
}

.steps p code {
  font-family: var(--mono);
  font-size: 0.88em;
  color: var(--ink);
}

/* A fingerprint is one long word, read character by character; it breaks
   anywhere rather than pushing the column wider than the screen. */
.steps p code.key-fingerprint { overflow-wrap: anywhere; word-break: break-all; }

@media (max-width: 52rem) {
  .steps li { grid-template-columns: minmax(0, 1fr); }
  .steps .command { grid-column: 1; grid-row: auto; margin-top: 1rem; }
}

/*
  The compose file, open under the steps. Read rather than copied, so it has
  no button: the copy that runs is the signed one in the release.
*/
.compose-view {
  margin: 1.2rem 0 0;
  padding: 1rem 1.4rem;
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: calc(var(--radius) + 2px);
}

.compose-view > h3 {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 600;
}

.compose-view > h3 code { font-family: var(--mono); font-size: 0.88em; }

.compose-view > p {
  max-width: var(--measure);
  margin: 0.8rem 0 1rem;
  font-size: 0.93rem;
  color: var(--ink-soft);
}

/* A domain the page shows rather than offers: the field's box, no control. */
.field-fixed {
  margin: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.field-fixed .field-note {
  font-weight: 400;
  color: var(--ink-soft);
}

/* A glossary inside prose: the term, then what it means, indented under it. */
.prose .terms {
  margin: 1.2rem 0;
}

.prose .terms dt {
  margin-top: 0.9rem;
  font-weight: 600;
  color: var(--ink);
}

.prose .terms dd {
  margin: 0.2rem 0 0 1.1rem;
  color: var(--ink-soft);
}

/* Four cards: two rows of two until there is room for one row. */
.cards-four { grid-template-columns: repeat(2, minmax(0, 1fr)); }

@media (min-width: 72rem) {
  .cards-four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

@media (max-width: 52rem) {
  .cards-four { grid-template-columns: 1fr; }
}

/* Three tabs always; one that was not chosen is there to say so, and is not
   a control. */
.tabs { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.tab.tab-off {
  cursor: default;
  background: transparent;
  border-style: dashed;
}

.tab.tab-off .tab-name,
.tab.tab-off .tab-says { color: var(--ink-faint); }

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

/*
  The footer's row sits in the middle of its band: the same space above and
  below, the mark and the links on one centre line, and no paragraph margin
  under the last line. It sat 29px from the rule and 48px from the bottom.
*/
.colophon-row { align-items: center; }
.colophon .colophon-row p { margin: 0; }

/*
  The lens on the front page.

  A layer under the opening's text, masked to a circle around the pointer: a
  barely-there wash of the brand colour, a grid of points, and a few words,
  all far quieter than anything a reader is meant to read. Closed, the circle
  has no radius, so nothing is drawn; hero.js opens it while a pointer is
  over the opening. The radius is a registered property so that it can ease
  open and shut; a browser without @property opens it at once.

  A second mask fades the whole layer out towards every edge of the opening,
  so the circle dissolves as it nears one instead of being cut by it. The
  first version was bright enough to catch the eye and stopped at the edge in
  a hard line.
*/
@property --lens-r {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}

.hero {
  position: relative;
  isolation: isolate;
}

.hero-lens {
  --lens-x: 70%;
  --lens-y: 40%;
  position: absolute;
  inset: 0 0 3rem;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  background-image:
    radial-gradient(circle 16rem at var(--lens-x) var(--lens-y),
      color-mix(in srgb, var(--brand) 5%, transparent), transparent 75%),
    radial-gradient(circle, color-mix(in srgb, var(--ink-faint) 22%, transparent) 1px, transparent 1.6px);
  background-size: 100% 100%, 24px 24px;
  -webkit-mask-image:
    radial-gradient(circle var(--lens-r) at var(--lens-x) var(--lens-y), #000 0%, rgb(0 0 0 / 0.4) 45%, transparent 100%),
    linear-gradient(to right, transparent, #000 18%, #000 82%, transparent),
    linear-gradient(to bottom, transparent, #000 22%, #000 78%, transparent);
  -webkit-mask-composite: source-in;
  mask-image:
    radial-gradient(circle var(--lens-r) at var(--lens-x) var(--lens-y), #000 0%, rgb(0 0 0 / 0.4) 45%, transparent 100%),
    linear-gradient(to right, transparent, #000 18%, #000 82%, transparent),
    linear-gradient(to bottom, transparent, #000 22%, #000 78%, transparent);
  mask-composite: intersect;
  transition: --lens-r 0.6s ease;
}

.hero-lit .hero-lens { --lens-r: 15rem; }

.hero-lens span {
  position: absolute;
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  color: color-mix(in srgb, var(--ink-faint) 45%, transparent);
  white-space: nowrap;
}

.hero-lens .lens-big {
  font-family: var(--sans);
  font-size: clamp(2rem, 4vw, 3.4rem);
  font-weight: 700;
  letter-spacing: -0.04em;
  color: color-mix(in srgb, var(--ink-faint) 28%, transparent);
}

.lens-1 { top: 20%; left: 60%; }
.lens-2 { top: 62%; left: 70%; }
.lens-3 { top: 30%; left: 82%; }
.lens-4 { top: 40%; left: 58%; }

@media (prefers-reduced-motion: reduce) {
  .hero-lens { transition: none; }
}

/*
  On a phone the footer's row wraps, and left-aligned it left the links in a
  corner with the width beside them empty. There it is one centred column:
  the mark and its line, then the links.
*/
@media (max-width: 40rem) {
  .colophon-row {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }
  .colophon-by,
  .colophon-row .colophon-links { justify-content: center; }
}

/* ── The workspace ─────────────────────────────────────────────────────
   An installation somebody runs: a rail of what can be done here beside the
   page. The demonstration keeps the site's masthead; none of this applies
   there, because its body carries no .workspace class. */

.workspace {
  display: grid;
  grid-template-columns: 15rem minmax(0, 1fr);
  min-height: 100vh;
}

.rail {
  position: sticky;
  top: 0;
  align-self: start;
  display: flex;
  flex-direction: column;
  height: 100vh;
  padding: 1.6rem 1rem;
  background: var(--paper-sunk);
  border-right: 1px solid var(--rule);
}

.rail-identity {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem;
  margin: 0 0.6rem 2.2rem;
}

.rail-identity .wordmark { font-size: 1.7rem; }

.rail-tag {
  padding: 0.2rem 0.4rem;
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 40%, var(--rule));
  border-radius: 3px;
  white-space: nowrap;
}

.rail-caption {
  margin: 0 0.75rem 0.6rem;
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.rail-nav {
  display: grid;
  gap: 0.25rem;
  margin-bottom: 1.8rem;
}

.rail-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 42px;
  padding: 0.55rem 0.75rem;
  font-size: 0.9rem;
  color: var(--ink-soft);
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
}

.rail-item:hover { color: var(--ink); background: var(--paper); }

.rail-item[aria-current="page"] {
  color: var(--ink);
  background: color-mix(in srgb, var(--brand) 10%, var(--paper));
  border-color: color-mix(in srgb, var(--brand) 35%, var(--rule));
}

.rail-glyph {
  width: 1.1rem;
  text-align: center;
  color: var(--ink-faint);
}

.rail-item[aria-current="page"] .rail-glyph { color: var(--brand); }

.rail-foot {
  margin-top: auto;
  padding: 1.2rem 0.75rem 0;
  border-top: 1px solid var(--rule);
}

.rail-foot .theme-toggle { margin: 0; }

.workspace-main {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.topbar {
  display: flex;
  align-items: center;
  min-height: 3.6rem;
  padding: 0 2rem;
  border-bottom: 1px solid var(--rule);
}

.topbar-path {
  margin: 0;
  font-size: 0.82rem;
  color: var(--ink-faint);
}

.topbar-path strong { font-weight: 500; color: var(--ink); }
.topbar-slash { margin: 0 0.35rem; }

.workspace main {
  width: 100%;
  max-width: 72rem;
  margin: 0;
  padding: 2rem 2rem 3rem;
  flex: 1;
}

.workspace .colophon {
  max-width: none;
  margin: 0;
  padding: 1rem 2rem;
}

.work-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.5rem;
  margin-bottom: 1.6rem;
}

.work-heading h1 {
  margin: 0 0 0.4rem;
  font-size: 1.75rem;
  letter-spacing: -0.02em;
}

.work-heading p {
  margin: 0;
  color: var(--ink-soft);
}

.work-help {
  margin: 1.2rem 0 0;
  max-width: var(--measure);
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.panel {
  padding: 1.4rem 1.5rem;
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: calc(var(--radius) + 2px);
}

.panel p { margin: 0 0 0.8rem; }
.panel p:last-child { margin-bottom: 0; }

.work-code {
  margin: 0;
  padding: 0.8rem 1rem;
  overflow-x: auto;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  font-family: var(--mono);
  font-size: 0.82rem;
}

/* New check: one card, two steps. */
.composer {
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: calc(var(--radius) + 2px);
  overflow: hidden;
}

.composer-part {
  margin: 0;
  padding: 1.4rem 1.5rem;
  border: 0;
}

.composer-part + .composer-part { border-top: 1px solid var(--rule); }

.composer-step {
  display: block;
  margin-bottom: 0.7rem;
  padding: 0;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/*
  The composer's two text fields are one column.

  The domain field was held to 28rem and the selector field below it to the
  measure, so the two inputs in one form ended on different lines, and the
  selector field kept the page's sunk background where the domain field had
  the paper one. Both now end at the measure and sit on the same ground.
*/
.composer-field {
  width: 100%;
  max-width: var(--measure);
  font-size: 1.1rem;
  background: var(--paper);
}

.composer .field { background: var(--paper); }

.composer-scope {
  display: flex;
  align-items: baseline;
  gap: 0.55rem;
  margin: 0.9rem 0 0;
  font-size: 0.86rem;
  color: var(--ink-soft);
}

.scope-dot {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--weak);
}

.scope-dot-on { background: var(--strong); }

.check-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.8rem;
}

.composer .check {
  margin: 0;
  background: var(--paper);
}

.composer-run {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 1.1rem;
}

.composer-run p {
  margin: 0;
  font-size: 0.86rem;
  color: var(--ink-soft);
}

.report-empty {
  margin-top: 1.5rem;
  padding: 2rem;
  text-align: center;
  color: var(--ink-soft);
  border: 1px dashed var(--rule);
  border-radius: calc(var(--radius) + 2px);
}

.report-empty p { margin: 0; font-size: 0.9rem; }

.report-empty-title {
  margin-bottom: 0.4rem;
  font-weight: 600;
  color: var(--ink);
}

.report-empty .report-empty-title { margin-bottom: 0.4rem; }

.console-results:not([hidden]) + .report-empty { display: none; }

/* Domains. */
.domain-form .field-row { max-width: 36rem; }
.domain-record { margin-top: 1rem; }
.domain-state { font-weight: 600; }

.workspace .state-grid { margin: 0; }

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

/* A phone: the rail becomes a bar of four across the top. */
@media (max-width: 46rem) {
  .workspace { display: block; }
  .rail {
    position: static;
    height: auto;
    padding: 1rem 1rem 0.5rem;
    border-right: 0;
    border-bottom: 1px solid var(--rule);
  }
  .rail-identity { margin: 0 0 0.9rem; }
  .rail-caption { display: none; }
  .rail-nav {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    margin-bottom: 0.5rem;
  }
  .rail-nav + .rail-caption + .rail-nav { grid-template-columns: 1fr; }
  .rail-item {
    flex-direction: column;
    justify-content: center;
    gap: 0.25rem;
    min-height: 3.4rem;
    padding: 0.4rem 0.2rem;
    font-size: 0.72rem;
    text-align: center;
  }
  .rail-foot { margin: 0; padding: 0.6rem 0 0.4rem; border: 0; }
  .topbar { padding: 0 1rem; min-height: 2.8rem; }
  .workspace main { padding: 1.2rem 1rem 2rem; }
  .work-heading { flex-direction: column; gap: 0.8rem; }
  .composer-part { padding: 1.1rem 1rem; }
  .composer-run { flex-direction: column; align-items: stretch; }
  .workspace .colophon { padding: 1rem; }
}

/* The checks in the composer are cards: the box and the name on one line,
   what the check reads under them, its rule set at the foot. */
.composer .checks { margin: 0; }

.composer-part > legend.composer-step {
  float: left;
  width: 100%;
}

.composer-part > legend.composer-step + * { clear: both; }

.check-grid .check {
  grid-template-columns: auto 1fr;
  grid-template-rows: auto 1fr auto;
  align-items: start;
  gap: 0.35rem 0.6rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}

.check-grid .check:last-child { border-bottom: 1px solid var(--rule); }

.check-grid .check input { margin: 0.2rem 0 0; }

.check-grid .check-name {
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 600;
}

.check-grid .check-says,
.check-grid .check-policy { grid-column: 2; }

.check-grid .check-says { font-size: 0.88rem; line-height: 1.5; }

.check-grid .check:has(input:checked) {
  border-color: color-mix(in srgb, var(--brand) 45%, var(--rule));
}

/*
  One row in the list is a door rather than a box.

  The name inventory is on the list because that is where somebody looks, and
  it is not a check: it asks about a whole domain rather than one host, it
  grades nothing, and it takes an address range this form has no field for. A
  box here ran two thirds of it and reported the rest as never asked, with
  nothing on the page to do about that, so the row opens the page that runs all
  of it instead.

  It keeps the card it would have had, minus the column the checkbox sat in,
  so the list still reads as one list.
*/
.check-grid .check-elsewhere,
.checks .check-elsewhere {
  text-decoration: none;
  color: inherit;
}

.check-grid .check-elsewhere { grid-template-columns: 1fr; }
.check-grid .check-elsewhere .check-says,
.check-grid .check-elsewhere .check-policy { grid-column: 1; }

.checks .check-elsewhere { grid-template-columns: minmax(5.5rem, auto) 1fr auto; }

.check-grid .check-elsewhere:hover,
.checks .check-elsewhere:hover {
  border-color: color-mix(in srgb, var(--brand) 45%, var(--rule));
}

/* A phone: four across and nothing else. The documents are in the footer,
   the colour switch sits beside the name, and a button repeating a rail item
   is left out. */
@media (max-width: 46rem) {
  .rail { position: relative; }
  .rail-identity { padding-right: 4.5rem; }
  .rail-nav + .rail-caption + .rail-nav { display: none; }
  .rail-foot {
    position: absolute;
    top: 1rem;
    right: 1rem;
    padding: 0;
  }
  .work-heading > .button { display: none; }
}

/* Signing in: the one page with no rail, a card in the middle. */
.workspace-signin { display: block; }

.workspace-signin main {
  max-width: 31rem;
  margin: 0 auto;
  padding-top: 12vh;
}

.signin-mark {
  margin: 0 0 1.4rem;
  text-align: center;
}

.signin-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.4rem;
}

.signin-head h1 { margin: 0; }

.signin-first {
  margin-top: 1.4rem;
  font-size: 0.88rem;
  color: var(--ink-soft);
}

.signin-first p { margin: 0 0 0.6rem; }
.signin-first .work-code { margin-bottom: 0.6rem; font-size: 0.78rem; }

.signin-mark .wordmark { font-size: 2rem; }

.signin-form,
.password-form {
  display: grid;
  gap: 0.6rem;
}

.signin-form h1 { font-size: 1.4rem; }
.signin-form .field,
.password-form .field { width: 100%; background: var(--paper); }
.signin-form .submit { margin-top: 0.4rem; }

.signin-status {
  min-height: 1.3em;
  margin: 0;
  font-size: 0.88rem;
  color: var(--ink-soft);
}

/*
  Empty, the status line takes no room.

  It held a line and a grid gap under the button for a message that is not
  there, so the card had twice the space below its button that it had above
  its title. Out of the flow rather than hidden: it is the live region a
  screen reader is listening to, and one that is not in the page when its
  message arrives is one that announces nothing.
*/
.signin-status:empty { position: absolute; }

/*
  The last paragraph a panel shows ends it, whatever hidden element follows.

  ".panel p:last-child" asked about the markup, and a panel whose empty-state
  sentence is followed by a table it has not filled yet kept that sentence's
  bottom margin: thirteen pixels more under the text than above it.
*/
.panel p:not(:has(~ :not([hidden]))) { margin-bottom: 0; }

.password-form {
  max-width: 30rem;
  margin-top: 1.2rem;
}

.password-form .section-title { margin: 0 0 0.4rem; }
.password-form .submit { justify-self: start; margin-top: 0.4rem; }

.rail-foot {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.topbar-signout {
  margin-left: auto;
  padding: 0.35rem 0.7rem;
  font: inherit;
  font-size: 0.82rem;
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  cursor: pointer;
}

.topbar-signout:hover { color: var(--ink); border-color: var(--ink-faint); }

/* A heading between the parts of one workspace page. */
.work-section {
  margin: 2rem 0 0.8rem;
  font-size: 1.05rem;
  font-weight: 600;
}

.work-heading + .work-section { margin-top: 0; }

.password-form { margin-top: 0; }

/* History: the list, and a report opened under it. */
.history-table { width: 100%; }
.history-table th { text-align: left; font-weight: 600; color: var(--ink-faint); font-size: 0.78rem; }
.history-table td { font-family: var(--sans); white-space: nowrap; vertical-align: middle; }
.history-target { font-weight: 600; white-space: normal; overflow-wrap: normal; }
.history-actions { text-align: right; white-space: nowrap; }
.history-status:empty, .history-status[hidden] { display: none; }
.history-empty { margin: 0; color: var(--ink-soft); }
.history-note { margin: 0 0 0.8rem; color: var(--ink-soft); }
.history-note[hidden] { display: none; }

.history-open,
.history-delete {
  margin-left: 0.4rem;
  padding: 0.3rem 0.7rem;
  font: inherit;
  font-size: 0.82rem;
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  cursor: pointer;
}

.history-open:hover { color: var(--ink); border-color: var(--ink-faint); }
.history-delete:hover { color: var(--insecure); border-color: var(--insecure); }
#history-report { margin-top: 1.5rem; }

/* What changed since the report kept before the one opened. */
.history-changes {
  margin: 0 0 1.5rem;
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}
.history-changes h3 { margin: 0 0 0.5rem; font-size: 0.95rem; }
.history-changes p { margin: 0 0 0.5rem; }
.history-changes ul { margin: 0 0 0.6rem; padding-left: 1.2rem; }
.history-changes-title { color: var(--ink-soft); }

/* The sign-in page's footer sits at the foot of the window, centred under the
   card like everything else on the page. It floated in the middle, flush
   left, because the page had no rail to stretch it. */
.workspace-signin .workspace-main { min-height: 100vh; }
.workspace-signin .colophon-row { justify-content: center; }

/* On a phone the command wraps rather than scrolling out of sight. */
@media (max-width: 46rem) {
  .signin-first .work-code code { white-space: pre-wrap; overflow-wrap: anywhere; }
}

/* A shell command, coloured the way a terminal would: the program, its
   flags, a quoted string and the pipe each told apart. Colour only; the
   text is the command exactly, so what is copied is what runs. */
.shell .sh-cmd  { color: var(--ink); font-weight: 700; }
.shell .sh-flag { color: var(--link); }
.shell .sh-str  { color: var(--strong); }
.shell .sh-op   { color: var(--ink-faint); }
.shell code     { color: var(--ink-soft); }

/*
  Both chains get the same columns, because they are the same measurement
  begun at two addresses.

  Each table used to size itself to its own contents, and the two never hold
  the same contents: the secure chain carries long https:// addresses and
  several hops, the plaintext one often a single short address. On a live
  report "Response" sat at the right edge in the first table and near the
  middle in the second, a hand's width apart, and the two rows a reader is
  meant to compare did not line up at all.

  The widths sit on the <col> elements for the reason the cipher tables give:
  a fixed layout takes its geometry from the columns first, and the header is
  set in a smaller size than the body, so a width written on a <th> would be
  measured in the wrong font.
*/
/*
  Both chains get the same columns, and the columns divide the whole width.

  Two attempts got this wrong in opposite directions. Sized by their contents,
  the two tables did not line up with each other. Capped at 62rem, the table
  stopped short of the panel it sits in, so every rule in it ended in mid-air
  while the section rule above ran the full width — a table that has been cut
  in half is what that looks like.

  Shares of the width settle both. The rules reach the same edge as everything
  else on the page, and the three columns are spaced by construction rather
  than by whatever is left over after one of them has taken what it wants.
*/
.chain {
  table-layout: fixed;
  width: 100%;
}

/*
  Half the width to the address, and the rest split between the two facts about
  the hop: how it was made, and what came back.

  Shares rather than characters, so the three add to the whole and none of them
  is the one that absorbs the difference. At the cell padding every table here
  shares, "plaintext" ended and "no response" began close enough that an eye
  going down the table had to separate them each time; a fifth of the width
  between their two edges says they are two answers and not one.

  The response column is the widest of the two because of what goes in it: a
  number, or "no response" with webprobe's own reason underneath, and a reason
  squeezed narrow arrives as a column of two-word lines.
*/
.chain .col-address   { width: 50%; }
.chain .col-transport { width: 20%; }
.chain .col-response  { width: 30%; }

/*
  On a phone a fifth of the width is narrower than the word in it.

  At 390 pixels the transport column came to 62 pixels, and "Transport" needs
  69 and "plaintext" 64: both ran on under "Response" and "308", in both
  chains, on every phone narrower than 430 pixels. So there the two short
  columns are given their longest content — "Transport" in the header's
  spaced capitals, "no response" below — and the address, which may break
  anywhere, takes what is left. The widths are still on the columns, so the
  two chains still line up with each other.
*/
@media (max-width: 34rem) {
  .chain .col-address   { width: auto; }
  .chain .col-transport { width: calc(10.5ch + 0.5rem); }
  .chain .col-response  { width: 11.5ch; }
}

/*
  An address is broken mid-word where it has to be. It is not an identifier
  somebody retypes — it is there to show where the site sent a browser — and
  the alternative is one long hop pushing the two columns beside it out of
  line, which is the fault this whole block exists to fix.
*/
.chain td.address {
  overflow-wrap: anywhere;
  word-break: break-all;
}

/*
  What a sheet of paper gets.

  A report is the thing somebody hands to an auditor, and until now the only
  way to take one away was the JSON, which is a file for a program. The browser
  prints what is on the screen; this block decides what "on the screen" means
  when the screen is A4.

  No PDF is written by this project. Writing one would mean a library, and
  there are none here, or writing a PDF writer — which can only use the fonts a
  reader already has, and would produce a document in different type with
  different rules and different colours from the report it claims to be.
*/
@media print {
  /*
    On paper the light scheme, whatever the screen was set to.

    A report printed from the dark scheme is a black rectangle: it empties a
    cartridge, and on most printers the pale text on it comes out grey on grey.
    The values are the light ones from :root, restated rather than referenced,
    because the dark ones are set on an attribute this cannot unset.
  */
  :root, :root[data-theme="dark"] {
    --paper:      #ffffff;
    --panel:      #ffffff;
    --ink:        #16181d;
    --ink-soft:   #3a3f4a;
    --ink-faint:  #5c6270;
    --rule:       #c9ccd4;
    --brand:      #c42b3a;
    color-scheme: light;
  }

  body, .workspace, .workspace-main, .console, .panel {
    background: #ffffff;
    color: #16181d;
  }

  /*
    What belongs to the screen and not to the document: the way around the
    installation, the things that are pressed, and the form that produced this.
    What is left is the report.

    Every name here was read off the page rather than guessed at — .work-main
    and .report-actions were both in this list and neither exists, which is a
    rule that does nothing and reads as one that does.
  */
  .rail, .masthead, .masthead-nav, .theme-toggle, .work-heading,
  .summary-actions, .composer, .checks, .colophon-links {
    display: none !important;
  }

  .workspace-main, .console, .console-results { padding: 0; margin: 0; max-width: none; }

  /*
    A table is read across, so it is not broken across sheets where that can be
    helped, and a heading is never left alone at the foot of one.
  */
  .rows, .grid, .panel, .check { break-inside: avoid; }
  tr { break-inside: avoid; }
  h1, h2, .section-title, .summary-target { break-after: avoid; }

  /* A link that is nothing but blue on paper. The one address worth printing
     is the method page, and it is named in the text beside it. */
  a { color: inherit; text-decoration: none; }

  /* A terminal on paper would be a black rectangle as well: the commands are
     printed in ink, and the window and its button stay on the screen. */
  .terminal {
    --term-bg: #ffffff; --term-bar: #ffffff; --term-rule: #c9ccd4;
    --term-ink: #16181d; --term-faint: #5c6270; --term-prompt: #16181d;
    --term-cmd: #16181d; --term-flag: #16181d; --term-str: #16181d; --term-var: #16181d;
    box-shadow: none;
  }
  .terminal-dots, .command-copy { display: none; }

  @page { margin: 18mm 16mm; }
}

/* Undertakings, each with the way to check it underneath.
   Like .terms, with more room: every term here is a whole sentence rather than
   a word, so the pairs need separating enough that a reader can see where one
   ends and the next begins. Deliberately not named for the word this project
   uses in prose: a retired standalone notice wore that word as a class, a test
   guards against its rule coming back, and a name that trips a guard written
   for a different reason is a name worth changing rather than a guard worth
   loosening. */
.prose .undertakings {
  margin: 1.4rem 0;
}

.prose .undertakings dt {
  margin-top: 1.5rem;
  font-weight: 600;
  color: var(--ink);
}

.prose .undertakings dt:first-child {
  margin-top: 0;
}

.prose .undertakings dd {
  margin: 0.45rem 0 0 0;
  padding-left: 0.9rem;
  border-left: 2px solid var(--rule);
  color: var(--ink-soft);
}
